From 9443eca6f0592cf46baf3249e4c75703e0496e16 Mon Sep 17 00:00:00 2001 From: "13621160019@163.com" <13621160019@163.com> Date: Fri, 16 Jul 2021 20:10:49 +0800 Subject: [PATCH] added Offline Cost File actions to API --- myems-api/MyEMS.postman_collection.json | 96 ++++++++++ myems-api/README.md | 35 +++- myems-api/app.py | 16 +- myems-api/core/offlinecostfile.py | 221 ++++++++++++++++++++++++ 4 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 myems-api/core/offlinecostfile.py diff --git a/myems-api/MyEMS.postman_collection.json b/myems-api/MyEMS.postman_collection.json index 56254ae4..924df9b3 100644 --- a/myems-api/MyEMS.postman_collection.json +++ b/myems-api/MyEMS.postman_collection.json @@ -3322,6 +3322,102 @@ } ] }, + { + "name": "Offline Cost File", + "item": [ + { + "name": "GET All Offline Cost Files", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/offlinecostfiles", + "host": [ + "{{base_url}}" + ], + "path": [ + "offlinecostfiles" + ] + } + }, + "response": [] + }, + { + "name": "GET a Offline Cost File by ID", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/offlinecostfiles/1", + "host": [ + "{{base_url}}" + ], + "path": [ + "offlinecostfiles", + "1" + ] + } + }, + "response": [] + }, + { + "name": "POST Upload a Offline Cost File", + "request": { + "method": "POST", + "header": [ + { + "key": "User_UUID", + "value": "dcdb67d1-6116-4987-916f-6fc6cf2bc0e4", + "type": "text" + }, + { + "key": "Token", + "value": "6b0622f8974b2e6f2d7a7470baf073b78bddffd4", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/zh/myems/myems-doc/offlinemeters.xlsx" + } + ] + }, + "url": { + "raw": "{{base_url}}/offlinecostfiles", + "host": [ + "{{base_url}}" + ], + "path": [ + "offlinecostfiles" + ] + } + }, + "response": [] + }, + { + "name": "DELETE a Offline Cost File by ID", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{base_url}}/offlinecostfiles/1", + "host": [ + "{{base_url}}" + ], + "path": [ + "offlinecostfiles", + "1" + ] + } + }, + "response": [] + } + ] + }, { "name": "Point", "item": [ diff --git a/myems-api/README.md b/myems-api/README.md index b7dbbcca..9dc7ef25 100644 --- a/myems-api/README.md +++ b/myems-api/README.md @@ -159,7 +159,7 @@ View in Postman: import the file MyEMS.postman_collection.json with Postman [Data Source](#Data-Source) | [Point](#Point) -[Tariff](#Tariff) | [Cost Center](#Cost-Center) +[Tariff](#Tariff) | [Cost Center](#Cost-Center) | [Offline Cost File](#Offline-Cost-File) [Meter](#Meter) | [Virtual Meter](#Virtual-Meter) | [Offline Meter](#Offline-Meter) | [Offline Meter File](#Offline-Meter-File) @@ -250,6 +250,37 @@ $ curl -i -H "Content-Type: application/json" -X POST -d '{"data":{"tariff_id":" $ curl -i -X DELETE {{base_url}}/costcenters/{id}/tariffs/{tid} ``` +### Offline Cost File +* GET an Offline Cost File by ID + +```bash +$ curl -i -X GET {{base_url}}/offlinecostfiles/{id} +``` +Result + +| Name | Data Type | Description | +|---------------|-----------|-------------------------------------------| +| id | integer | Offline Cost File ID | +| file_name | string | Offline Cost File name | +| uuid | string | Offline Cost File UUID | +| upload_datetime | float | the number of milliseconds since January 1, 1970, 00:00:00, universal time | +| status | string | Offline Cost File processing status (new, done, error) | +| file_object | BLOB | Offline Cost File Object | + +* GET All Offline Cost Files +```bash +$ curl -i -X GET {{base_url}}/offlinecostfiles +``` +* DELETE an Offline Cost File by ID +```bash +$ curl -i -X DELETE {{base_url}}/offlinecostfiles/{id} +``` +* POST Upload an Offline Cost File + (user must login first to get cookie) +```bash +$ curl -i -H "Content-Type: application/TBD" -X POST -d 'file: (binary)' {{base_url}}/offlinecostfiles +``` + ### Data Source * GET Data Source by ID @@ -1076,7 +1107,7 @@ $ curl -i -X DELETE {{base_url}}/offlinemeterfiles/{id} * POST Upload an Offline Meter File (user must login first to get cookie) ```bash -$ curl -i -H "Content-Type: application/TBD" -X POST -d 'file: (binary)' {{base_url}}/offlinemeters +$ curl -i -H "Content-Type: application/TBD" -X POST -d 'file: (binary)' {{base_url}}/offlinemeterfiles ``` ### Point diff --git a/myems-api/app.py b/myems-api/app.py index 4a31b9b5..7cf363e9 100644 --- a/myems-api/app.py +++ b/myems-api/app.py @@ -3,12 +3,13 @@ from falcon_cors import CORS from falcon_multipart.middleware import MultipartMiddleware from core import energyflowdiagram, privilege, textmessage, distributioncircuit, virtualmeter, \ costcenter, point, knowledgefile, meter, gsmmodem, tariff, user, storetype, timezone, \ - offlinemeterfile, version, contact, emailserver, combinedequipment, datasource, equipment, tenant, shopfloor, \ + offlinecostfile, offlinemeterfile, version, contact, emailserver, combinedequipment, datasource, equipment, tenant, shopfloor, \ webmessage, distributionsystem, store, emailmessage, tenanttype, wechatmessage, space, gateway, offlinemeter, \ rule, energycategory, sensor, energyitem, notification from reports import advancedreport from reports import distributionsystem as distributionsystemreport from reports import energyflowdiagram as energyflowdiagramreport +from reports import combinedequipmentbatch from reports import combinedequipmentcost from reports import combinedequipmentefficiency from reports import combinedequipmentenergycategory @@ -18,8 +19,8 @@ from reports import combinedequipmentload from reports import combinedequipmentoutput from reports import combinedequipmentsaving from reports import combinedequipmentstatistics -from reports import combinedequipmentbatch from reports import dashboard +from reports import equipmentbatch from reports import equipmentcost from reports import equipmentefficiency from reports import equipmentenergycategory @@ -139,6 +140,11 @@ api.add_route('/costcenters/{id_}/tariffs', api.add_route('/costcenters/{id_}/tariffs/{tid}', costcenter.CostCenterTariffItem()) +api.add_route('/offlinecostfiles', + offlinecostfile.OfflineCostFileCollection()) +api.add_route('/offlinecostfiles/{id_}', + offlinecostfile.OfflineCostFileItem()) + api.add_route('/datasources', datasource.DataSourceCollection()) api.add_route('/datasources/{id_}', @@ -483,6 +489,8 @@ api.add_route('/reports/distributionsystem', distributionsystemreport.Reporting()) api.add_route('/reports/energyflowdiagram', energyflowdiagramreport.Reporting()) +api.add_route('/reports/combinedequipmentbatch', + combinedequipmentbatch.Reporting()) api.add_route('/reports/combinedequipmentcost', combinedequipmentcost.Reporting()) api.add_route('/reports/combinedequipmentefficiency', @@ -501,10 +509,10 @@ api.add_route('/reports/combinedequipmentsaving', combinedequipmentsaving.Reporting()) api.add_route('/reports/combinedequipmentstatistics', combinedequipmentstatistics.Reporting()) -api.add_route('/reports/combinedequipmentbatch', - combinedequipmentbatch.Reporting()) api.add_route('/reports/dashboard', dashboard.Reporting()) +api.add_route('/reports/equipmentbatch', + equipmentbatch.Reporting()) api.add_route('/reports/equipmentcost', equipmentcost.Reporting()) api.add_route('/reports/equipmentefficiency', diff --git a/myems-api/core/offlinecostfile.py b/myems-api/core/offlinecostfile.py new file mode 100644 index 00000000..875f5eab --- /dev/null +++ b/myems-api/core/offlinecostfile.py @@ -0,0 +1,221 @@ +import falcon +import json +import mysql.connector +import config +import uuid +from datetime import datetime, timezone +import os + + +class OfflineCostFileCollection: + @staticmethod + def __init__(): + pass + + @staticmethod + def on_options(req, resp): + resp.status = falcon.HTTP_200 + + @staticmethod + def on_get(req, resp): + cnx = mysql.connector.connect(**config.myems_historical_db) + cursor = cnx.cursor() + + query = (" SELECT id, file_name, uuid, upload_datetime_utc, status " + " FROM tbl_offline_cost_files " + " ORDER BY upload_datetime_utc desc ") + cursor.execute(query) + rows = cursor.fetchall() + cursor.close() + cnx.disconnect() + + result = list() + if rows is not None and len(rows) > 0: + for row in rows: + upload_datetime = row[3] + upload_datetime = upload_datetime.replace(tzinfo=timezone.utc) + meta_result = {"id": row[0], + "file_name": row[1], + "uuid": row[2], + "upload_datetime": upload_datetime.timestamp() * 1000, + "status": row[4]} + result.append(meta_result) + + resp.body = json.dumps(result) + + @staticmethod + def on_post(req, resp): + """Handles POST requests""" + try: + upload = req.get_param('file') + # Read upload file as binary + raw_blob = upload.file.read() + # Retrieve filename + filename = upload.filename + file_uuid = str(uuid.uuid4()) + + # Define file_path + file_path = os.path.join(config.upload_path, file_uuid) + + # Write to a temporary file to prevent incomplete files from + # being used. + temp_file_path = file_path + '~' + + open(temp_file_path, 'wb').write(raw_blob) + + # Now that we know the file has been fully saved to disk + # move it into place. + os.rename(temp_file_path, file_path) + except Exception as ex: + raise falcon.HTTPError(falcon.HTTP_400, title='API.ERROR', + description='API.FAILED_TO_UPLOAD_OFFLINE_COST_FILE') + + # Verify User Session + token = req.headers.get('TOKEN') + user_uuid = req.headers.get('USER-UUID') + if token is None: + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.TOKEN_NOT_FOUND_IN_HEADERS_PLEASE_LOGIN') + if user_uuid is None: + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.USER_UUID_NOT_FOUND_IN_HEADERS_PLEASE_LOGIN') + + cnx = mysql.connector.connect(**config.myems_user_db) + cursor = cnx.cursor() + + query = (" SELECT utc_expires " + " FROM tbl_sessions " + " WHERE user_uuid = %s AND token = %s") + cursor.execute(query, (user_uuid, token,)) + row = cursor.fetchone() + + if row is None: + if cursor: + cursor.close() + if cnx: + cnx.disconnect() + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_SESSION_PLEASE_RE_LOGIN') + else: + utc_expires = row[0] + if datetime.utcnow() > utc_expires: + if cursor: + cursor.close() + if cnx: + cnx.disconnect() + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.USER_SESSION_TIMEOUT') + + cursor.execute(" SELECT id " + " FROM tbl_users " + " WHERE uuid = %s ", + (user_uuid,)) + row = cursor.fetchone() + if row is None: + if cursor: + cursor.close() + if cnx: + cnx.disconnect() + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_USER_PLEASE_RE_LOGIN') + else: + user_id = row[0] + + cnx = mysql.connector.connect(**config.myems_historical_db) + cursor = cnx.cursor() + + add_values = (" INSERT INTO tbl_offline_cost_files " + " (file_name, uuid, upload_datetime_utc, status, file_object ) " + " VALUES (%s, %s, %s, %s, %s) ") + cursor.execute(add_values, (filename, + file_uuid, + datetime.utcnow(), + 'new', + raw_blob)) + new_id = cursor.lastrowid + cnx.commit() + cursor.close() + cnx.disconnect() + + resp.status = falcon.HTTP_201 + resp.location = '/offlinecostfiles/' + str(new_id) + + +class OfflineCostFileItem: + @staticmethod + def __init__(): + pass + + @staticmethod + def on_options(req, resp, id_): + resp.status = falcon.HTTP_200 + + @staticmethod + def on_get(req, resp, id_): + if not id_.isdigit() or int(id_) <= 0: + raise falcon.HTTPError(falcon.HTTP_400, + title='API.BAD_REQUEST', + description='API.INVALID_OFFLINE_COST_FILE_ID') + + cnx = mysql.connector.connect(**config.myems_historical_db) + cursor = cnx.cursor() + + query = (" SELECT id, file_name, uuid, upload_datetime_utc, status " + " FROM tbl_offline_cost_files " + " WHERE id = %s ") + cursor.execute(query, (id_,)) + row = cursor.fetchone() + cursor.close() + cnx.disconnect() + if row is None: + raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', + description='API.OFFLINE_COST_FILE_NOT_FOUND') + + upload_datetime = row[3] + upload_datetime = upload_datetime.replace(tzinfo=timezone.utc) + + result = {"id": row[0], + "file_name": row[1], + "uuid": row[2], + "upload_datetime": upload_datetime.timestamp() * 1000, + "status": row[4]} + resp.body = json.dumps(result) + + @staticmethod + def on_delete(req, resp, id_): + if not id_.isdigit() or int(id_) <= 0: + raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST', + description='API.INVALID_OFFLINE_COST_FILE_ID') + + cnx = mysql.connector.connect(**config.myems_historical_db) + cursor = cnx.cursor() + + cursor.execute(" SELECT uuid " + " FROM tbl_offline_cost_files " + " WHERE id = %s ", (id_,)) + row = cursor.fetchone() + if row is None: + cursor.close() + cnx.disconnect() + raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND', + description='API.OFFLINE_COST_FILE_NOT_FOUND') + + try: + file_uuid = row[0] + # Define file_path + file_path = os.path.join(config.upload_path, file_uuid) + + # remove the file from disk + os.remove(file_path) + except Exception as ex: + # ignore exception and don't return API.OFFLINE_COST_FILE_NOT_FOUND error + pass + + # Note: the energy data imported from the deleted file will not be deleted + cursor.execute(" DELETE FROM tbl_offline_cost_files WHERE id = %s ", (id_,)) + cnx.commit() + + cursor.close() + cnx.disconnect() + + resp.status = falcon.HTTP_204