From 672a963ea62eeb73ac551e09506aa3824632129e Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Fri, 30 Dec 2022 21:51:40 +0000 Subject: [PATCH 1/3] Integrate large format printer with Protocoin --- apiserver/apiserver/api/throttles.py | 3 + apiserver/apiserver/api/views.py | 125 ++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/apiserver/apiserver/api/throttles.py b/apiserver/apiserver/api/throttles.py index e2aeb57..5eadb37 100644 --- a/apiserver/apiserver/api/throttles.py +++ b/apiserver/apiserver/api/throttles.py @@ -24,6 +24,9 @@ class LoggingThrottle(throttling.BaseThrottle): return True elif path == '/sessions/' and user == None: return True + elif path == '/protocoin/printer_report/': + logging.info('%s %s | User: %s | Data: [XML]', method, path, user) + return True if request.data: if type(request.data) is not dict: diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index 9ff500f..ef99f5c 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -23,8 +23,7 @@ import icalendar import datetime, time import io import csv - -import requests +import xmltodict from . import models, serializers, utils, utils_paypal, utils_stats, utils_ldap, utils_email from .permissions import ( @@ -1258,6 +1257,128 @@ class ProtocoinViewSet(Base): ) return Response(res) + @action(detail=False, methods=['post']) + def printer_report(self, request, pk=None): + try: + with transaction.atomic(): + #auth_token = request.META.get('HTTP_AUTHORIZATION', '') + #if secrets.VEND_API_TOKEN and auth_token != 'Bearer ' + secrets.VEND_API_TOKEN: + # raise exceptions.PermissionDenied() + + xml_start = len('XML_string=') + xml_string = request.body.decode()[xml_start:] + report_json = xmltodict.parse(xml_string) + + jobs = report_json['xdm:Device']['xdm:Metrics']['xdm:JobHistory']['xdm:Job'] + jobs.sort(key=lambda x: x['job:Job']['job:Processing']['pwg:DateTimeAtCreation'], reverse=True) + + logging.info('Sorted %s jobs by creation date.', str(len(jobs))) + + #import json + #print(json.dumps(jobs, indent=4)) + + # most recent job might be an automatic service job + # so iterate until we find a userIO job + for job in jobs: + previous_job = job['job:Job'] + source = previous_job['job:Source'].get('dd:JobSource', None) + if source == 'userIO': break + + job_uuid = previous_job.get('dd:UUID', None) + username = previous_job['job:Source']['job:Client'].get('dd:UserName', None) + + logging.info('New printer job UUID: %s, username: %s', str(job_uuid), str(username)) + + if not job_uuid: + msg = 'Missing job UUID, aborting.' + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + tx = models.Transaction.objects.filter(reference_number=job_uuid) + if tx.exists(): + msg = 'Job {}: already billed for in transaction {}, aborting.'.format(job_uuid, tx[0].id) + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + if not username: + msg = 'Job {}: missing username, aborting.'.format(job_uuid) + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + is_completed = previous_job.get('dd:EndState', None) == 'Completed' + is_print = previous_job.get('dd:JobType', None) == 'print' + + if not is_completed: + msg = 'Job {} user {}: not complete, aborting.'.format(job_uuid, username) + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + if not is_print: + msg = 'Job {} user {}: not a print, aborting.'.format(job_uuid, username) + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + try: + user = User.objects.get(username__iexact=username) + except User.DoesNotExist: + msg = 'Job {}: unable to find username {}, aborting.'.format(job_uuid, username) + utils.alert_tanner(msg) + logger.error(msg) + return Response(200) + + INK_PROTOCOIN_PER_ML = 0.20 + PAPER_PROTOCOIN_PER_M = 0.25 + PROTOCOIN_PER_PRINT = 2.0 + + total_cost = PROTOCOIN_PER_PRINT + logging.info(' Fixed cost: %s', str(PROTOCOIN_PER_PRINT)) + + counters = previous_job['job:Processing']['job:JobTotals']['count:Counter'] + + for counter in counters: + if counter['dd:CounterTarget'] == 'inkUsed': + microliters = float(counter['dd:ValueFloat']) + millilitres = microliters / 1000.0 + cost = millilitres * INK_PROTOCOIN_PER_ML + total_cost += cost + logging.info(' %s ink cost: %s', counter['dd:MarkerColor'], str(cost)) + elif counter['dd:CounterTarget'] == 'mediaFed': + squareinches = float(counter['dd:ValueFloat']) + squaremetres = squareinches / 1550.0 + cost = squaremetres * PAPER_PROTOCOIN_PER_M + total_cost += cost + logging.info(' Paper cost: %s', str(cost)) + + total_cost = round(total_cost, 2) + + logging.info('Total cost: %s protocoin', str(total_cost)) + + memo = 'Protocoin - Purchase spent ₱ {} on printing'.format( + total_cost, + ) + + tx = models.Transaction.objects.create( + user=user, + protocoin=-total_cost, + amount=0, + number_of_membership_months=0, + account_type='Protocoin', + category='Consumables', + info_source='System', + reference_number=job_uuid, + memo=memo, + ) + utils.log_transaction(tx) + + return Response(200) + except OperationalError: + self.printer_report(request, pk) + class PinballViewSet(Base): @action(detail=False, methods=['post']) From 7c0b44477ac34192c519359f6f87f41bb1a043f7 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Mon, 13 Feb 2023 18:25:01 +0000 Subject: [PATCH 2/3] Switch printer report API to use parsed emails --- apiserver/apiserver/api/throttles.py | 3 -- apiserver/apiserver/api/views.py | 61 +++++++++++----------------- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/apiserver/apiserver/api/throttles.py b/apiserver/apiserver/api/throttles.py index 5eadb37..e2aeb57 100644 --- a/apiserver/apiserver/api/throttles.py +++ b/apiserver/apiserver/api/throttles.py @@ -24,9 +24,6 @@ class LoggingThrottle(throttling.BaseThrottle): return True elif path == '/sessions/' and user == None: return True - elif path == '/protocoin/printer_report/': - logging.info('%s %s | User: %s | Data: [XML]', method, path, user) - return True if request.data: if type(request.data) is not dict: diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index 8464c05..62fc35e 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -1285,27 +1285,11 @@ class ProtocoinViewSet(Base): #if secrets.VEND_API_TOKEN and auth_token != 'Bearer ' + secrets.VEND_API_TOKEN: # raise exceptions.PermissionDenied() - xml_start = len('XML_string=') - xml_string = request.body.decode()[xml_start:] - report_json = xmltodict.parse(xml_string) + # {'job_name': 'download.png', 'uuid': '6abbad4d-dda3-4954-b4f1-ac77933a0562', 'timestamp': '20230211173624', + # 'job_status': '0', 'user_name': 'Tanner.Collin', 'source': '1', 'paper_name': 'Plain Paper', 'paper_sqi': '356', 'ink_ul': '54'} - jobs = report_json['xdm:Device']['xdm:Metrics']['xdm:JobHistory']['xdm:Job'] - jobs.sort(key=lambda x: x['job:Job']['job:Processing']['pwg:DateTimeAtCreation'], reverse=True) - - logging.info('Sorted %s jobs by creation date.', str(len(jobs))) - - #import json - #print(json.dumps(jobs, indent=4)) - - # most recent job might be an automatic service job - # so iterate until we find a userIO job - for job in jobs: - previous_job = job['job:Job'] - source = previous_job['job:Source'].get('dd:JobSource', None) - if source == 'userIO': break - - job_uuid = previous_job.get('dd:UUID', None) - username = previous_job['job:Source']['job:Client'].get('dd:UserName', None) + job_uuid = request.data['uuid'] + username = request.data['user_name'] logging.info('New printer job UUID: %s, username: %s', str(job_uuid), str(username)) @@ -1328,8 +1312,8 @@ class ProtocoinViewSet(Base): logger.error(msg) return Response(200) - is_completed = previous_job.get('dd:EndState', None) == 'Completed' - is_print = previous_job.get('dd:JobType', None) == 'print' + is_completed = request.data['job_status'] == '0' + is_print = request.data['source'] == '1' if not is_completed: msg = 'Job {} user {}: not complete, aborting.'.format(job_uuid, username) @@ -1352,34 +1336,35 @@ class ProtocoinViewSet(Base): return Response(200) INK_PROTOCOIN_PER_ML = 0.20 - PAPER_PROTOCOIN_PER_M = 0.25 + DEFAULT_PAPER_PROTOCOIN_PER_M = 0.25 PROTOCOIN_PER_PRINT = 2.0 total_cost = PROTOCOIN_PER_PRINT logging.info(' Fixed cost: %s', str(PROTOCOIN_PER_PRINT)) - counters = previous_job['job:Processing']['job:JobTotals']['count:Counter'] + microliters = float(request.data['ink_ul']) + millilitres = microliters / 1000.0 + cost = millilitres * INK_PROTOCOIN_PER_ML + total_cost += cost + logging.info(' %s ul ink cost: %s', str(microliters), str(cost)) - for counter in counters: - if counter['dd:CounterTarget'] == 'inkUsed': - microliters = float(counter['dd:ValueFloat']) - millilitres = microliters / 1000.0 - cost = millilitres * INK_PROTOCOIN_PER_ML - total_cost += cost - logging.info(' %s ink cost: %s', counter['dd:MarkerColor'], str(cost)) - elif counter['dd:CounterTarget'] == 'mediaFed': - squareinches = float(counter['dd:ValueFloat']) - squaremetres = squareinches / 1550.0 - cost = squaremetres * PAPER_PROTOCOIN_PER_M - total_cost += cost - logging.info(' Paper cost: %s', str(cost)) + PAPER_COSTS = { + 'Plain Paper': 0.25, + } + + squareinches = float(request.data['paper_sqi']) + squaremetres = squareinches / 1550.0 + cost = squaremetres * PAPER_COSTS.get(request.data['paper_name'], DEFAULT_PAPER_PROTOCOIN_PER_M) + total_cost += cost + logging.info(' %s sqi paper cost: %s', str(squareinches), str(cost)) total_cost = round(total_cost, 2) logging.info('Total cost: %s protocoin', str(total_cost)) - memo = 'Protocoin - Purchase spent ₱ {} on printing'.format( + memo = 'Protocoin - Purchase spent ₱ {} printing {}'.format( total_cost, + request.data['job_name'], ) tx = models.Transaction.objects.create( From d946348fec610ff7588613ea52304438f137bb16 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Mon, 13 Feb 2023 23:24:37 +0000 Subject: [PATCH 3/3] Adjust prices, add negative protocoin warning --- apiserver/apiserver/api/views.py | 7 +++++-- webclient/src/Home.js | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index 62fc35e..3c36a17 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -1312,6 +1312,9 @@ class ProtocoinViewSet(Base): logger.error(msg) return Response(200) + # status 0 = complete + # status 3 = cancelled + is_completed = request.data['job_status'] == '0' is_print = request.data['source'] == '1' @@ -1335,8 +1338,8 @@ class ProtocoinViewSet(Base): logger.error(msg) return Response(200) - INK_PROTOCOIN_PER_ML = 0.20 - DEFAULT_PAPER_PROTOCOIN_PER_M = 0.25 + INK_PROTOCOIN_PER_ML = 0.75 + DEFAULT_PAPER_PROTOCOIN_PER_M = 0.50 PROTOCOIN_PER_PRINT = 2.0 total_cost = PROTOCOIN_PER_PRINT diff --git a/webclient/src/Home.js b/webclient/src/Home.js index d23558e..cd1398a 100644 --- a/webclient/src/Home.js +++ b/webclient/src/Home.js @@ -22,6 +22,11 @@ function MemberInfo(props) { return (
+ {member.protocoin < 0 && + Your Protocoin balance is negative! +

Visit the Paymaster page or pay a Director to buy Protocoin.

+
} + stats && stats.track && stats.track[x] ? moment.unix(stats.track[x]['time']).tz('America/Edmonton').fromNow() : ''; const getTrackName = (x) => stats && stats.track && stats.track[x] && stats.track[x]['first_name'] ? stats.track[x]['first_name'] : 'Unknown'; - const alarmStat = () => stats && stats.alarm && moment().unix() - stats.alarm['time'] < 300 ? stats.alarm['data'] < 270 ? 'Armed' : 'Disarmed' : 'Unknown'; + //const alarmStat = () => stats && stats.alarm && moment().unix() - stats.alarm['time'] < 300 ? stats.alarm['data'] < 270 ? 'Armed' : 'Disarmed' : 'Unknown'; + const alarmStat = () => 'Unknown'; - const doorOpenStat = () => alarmStat() === 'Disarmed' && stats.alarm['data'] > 360 ? ', door open' : ''; + //const doorOpenStat = () => alarmStat() === 'Disarmed' && stats.alarm['data'] > 360 ? ', door open' : ''; + const doorOpenStat = () => ''; const show_signup = stats?.at_protospace;