diff --git a/bootstrap.py b/bootstrap.py index fd54fa4..5875f68 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -19,9 +19,6 @@ from hub_api.greenery_catalog import GreeneryCatalogEntry, GreeneryCatalogEntrie from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive from hub_api.uptime import Uptime from hub_api.usage_catalog import UsageCatalogEntry, UsageCatalogEntries, UsageCatalogNames -from hub_api.user import User, UserLogin -from hub_api.city_info import CityInfo, City -from hub_api.city_commands import Update, Delete, Search, List, Save sh.begin_time = datetime.datetime.now() app = flask.Flask('cerc_api') @@ -42,19 +39,9 @@ api.add_resource(UsageCatalogEntries, '/v1.4/usage-catalog/entries') api.add_resource(UsageCatalogNames, '/v1.4/usage-catalog/names') # Session -api.add_resource(User, '/v1.4/user') -api.add_resource(UserLogin, '/v1.4/user/login') api.add_resource(SessionStart, '/v1.4/session/start') api.add_resource(SessionEnd, '/v1.4/session/end') api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive') -api.add_resource(CityInfo, '/v1.4/city_info') -api.add_resource(City, '/v1.4/city') -api.add_resource(Save, '/v1.4/city/save_city') -api.add_resource(Update, '/v1.4/city/update_city') -api.add_resource(Delete, '/v1.4/city/delete_city') -api.add_resource(List, '/v1.4/city/list_cities') -api.add_resource(Search, '/v1.4/city/search_city') - with open("hub_api/docs/openapi-specs.yml", "r") as stream: swagger_config = { diff --git a/hub_api/city_info.py b/hub_api/city_info.py index 2249931..e0f3f3d 100644 --- a/hub_api/city_info.py +++ b/hub_api/city_info.py @@ -15,147 +15,71 @@ from pathlib import Path import os from hub_api.config import Config -headers = {'Content-Type': 'application/json'} - class CityInfo(Resource, Config): def __init__(self): super().__init__() + @role_required([UserRoles.Admin.value]) def get(self, city_id): - """ - API call to search database for city by city_id - :return: json, status code, headers - """ city = self.get_city(city_id) - if city is not None: - return Response(response=json.dumps({ - 'id': city.id, 'name': city.name, 'srs_name': city.srs_name, - 'time_zone': city.time_zone, 'version': city.city_version, 'country': city.country_code, - 'lat': city.latitude, 'lon': city.longitude, 'lower_corner': city.lower_corner, - 'upper_corner': city.upper_corner, 'created': city.created, 'updated': city.updated, - 'user': {'id': city.user.id, 'name': city.user.name, 'email': city.user.email, - 'role': city.user.role.value} - }, default=str), status=200, headers=headers) - return Response(response=json.dumps({'err_msg': 'City not found'}), status=404, headers=headers) - @role_required([UserRoles.Admin.value]) - def put(self, city_id): - """ - API call to update city in database with new city_file by city_id - :return: json, status code, headers - """ - allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'} - try: - city_file = request.files['city_file'] - ext = city_file.filename.rsplit('.', 1)[1].lower() + # TODO: this is only for dompark project and need to be removed in future versions. + floor_area = 0 + wall_construction = 'unknown' + floor_construction = 'unknown' + roof_construction = 'unknown' + window_type = 'unknown' + building_dic = {} + for building in city.buildings: - if ext in allowed_ext: - city_file_type = ext - if ext == 'gml': - city_file_type = 'citygml' - elif ext == '3dm': - city_file_type = 'rhino' - - file_path = ( - Path(__file__).parent.parent / 'data/uploaded_city/{}'.format(city_file.filename)).resolve() - city_file.save(file_path) - city = GeometryFactory(city_file_type, file_path).city - - saved_city = self.import_db_factory.update_city(city_id, city) - if os.path.exists(file_path): - os.remove(file_path) - if saved_city is None: - saved_city = self.export_db_factory.get_city(city_id) - return Response(headers=headers, response=json.dumps({ - 'id': saved_city.id, 'name': saved_city.name, 'srs_name': saved_city.srs_name, - 'time_zone': saved_city.time_zone, 'version': saved_city.city_version, - 'country': saved_city.country_code, - 'lat': saved_city.latitude, 'lon': saved_city.longitude, - 'lower_corner': saved_city.lower_corner, - 'upper_corner': saved_city.upper_corner, 'created': saved_city.created, - 'updated': saved_city.updated, - 'user': {'id': saved_city.user.id, 'name': saved_city.user.name, 'email': saved_city.user.email, - 'role': saved_city.user.role.value} - }, default=str), status=201) - return Response(response=json.dumps(saved_city), status=200) - else: - return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400) - - except Exception as err: - logger.error(err) - return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while updating city'}), status=400) - - @role_required([UserRoles.Admin.value]) - def delete(self, city_id): - """ - API call to delete city from database by city_id - :return: json, status code, headers - """ - try: - return Response(headers=headers, response=json.dumps( - self.import_db_factory.delete_city(city_id=city_id)), status=201) - except Exception as err: - logger.error(err) - return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while deleting city'}), status=400) - -class CitySearch(Resource, Config): - """ - CitySearch class for searching for cities via an API call - """ - def __init__(self): - super().__init__() - - def get(self, search_term, query): - """ - API call to search city depending on search_term and specified query - :return: json, status code, headers - """ - if search_term == "city_name": - city = self.get_city_by_name(query) - - if city is not None: - return Response(response=json.dumps({ - 'id': city.id, 'name': city.name, 'srs_name': city.srs_name, - 'time_zone': city.time_zone, 'version': city.city_version, 'country': city.country_code, - 'lat': city.latitude, 'lon': city.longitude, 'lower_corner': city.lower_corner, - 'upper_corner': city.upper_corner, 'created': city.created, 'updated': city.updated, - 'user': {'id': city.user.id, 'name': city.user.name, 'email': city.user.email, - 'role': city.user.role.value} - }, default=str), status=200, headers=headers) - return Response(response=json.dumps({'err_msg': 'City not found'}), status=404, headers=headers) - - elif search_term == "user_id": - cities = self.get_city_by_user(query) - - if cities is not None: - for city in cities: - #TODO - iterate through cities packaging them into a list and return the packaged JSON - return Response(response=json.dumps({ - 'id': city.id, 'name': city.name, 'srs_name': city.srs_name, - 'time_zone': city.time_zone, 'version': city.city_version, 'country': city.country_code, - 'lat': city.latitude, 'lon': city.longitude, 'lower_corner': city.lower_corner, - 'upper_corner': city.upper_corner, 'created': city.created, 'updated': city.updated, - 'user': {'id': city.user.id, 'name': city.user.name, 'email': city.user.email, - 'role': city.user.role.value} - }, default=str), status=200, headers=headers) - return Response(response=json.dumps({'err_msg': 'No cities found by user'}), status=404, headers=headers) - - elif search_term == "list_all": - #TODO - implement API call to query all define cities in the database - pass + usages = [] # This is only valid for dompark project as all the building have the same usage + if building.lower_corner[2] == 0: + floor_area += building.floor_area + for internal_zone in building.internal_zones: + for usage_zone in internal_zone.usage_zones: + usages.append({'percentage': usage_zone.percentage, 'usage': usage_zone.usage}) + for thermal_zone in internal_zone.thermal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + if thermal_boundary.parent_surface.type == 'Ground': + floor_construction = thermal_boundary.construction_name + elif thermal_boundary.parent_surface.type == 'Wall': + wall_construction = thermal_boundary.construction_name + for thermal_opening in thermal_boundary.thermal_openings: + if thermal_opening.construction_name is not None: + window_type = thermal_opening.construction_name + break + else: + roof_construction = thermal_boundary.construction_name + name = building.human_readable_name + year_of_construction = str(building.year_of_construction) + building_dic = { + 'name': str(name), + 'floor_area': str(floor_area), + 'year_of_construction': str(year_of_construction), + 'usages': usages, + 'wall_construction': wall_construction, + 'floor_construction': floor_construction, + 'roof_construction': roof_construction, + 'window_type': window_type, + 'default_archetype': 'industry ASHRAE_2004:4A non_standard_dompark' + } + buildings = [building_dic] + response = {'city_name': city.name, + 'climate_reference_city': str(city.climate_reference_city), + 'buildings': buildings + } + return Response(json.dumps(response), status=200) class City(Resource, Config): + def __init__(self): + super().__init__() @role_required([UserRoles.Admin.value]) def post(self): - """ - API call to create city in database with newcity_file by city_id - :return: json, status code, headers - """ allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'} try: city_file = request.files['city_file'] @@ -184,10 +108,9 @@ class City(Resource, Config): 'user': {'id': saved_city.user.id, 'name': saved_city.user.name, 'email': saved_city.user.email, 'role': saved_city.user.role.value} }, default=str), status=201) - return Response(response=json.dumps(saved_city), status=200, headers=headers) + return Response(response=json.dumps(saved_city), status=200) else: - return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400, headers=headers) + return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400) except Exception as err: logger.error(err) - return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), - status=400, headers=headers) \ No newline at end of file + return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), status=400) diff --git a/hub_api/config.py b/hub_api/config.py index efa2985..48a8758 100644 --- a/hub_api/config.py +++ b/hub_api/config.py @@ -3,41 +3,28 @@ Config SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2023 Project Peter Yefi peteryefi@gmail.com """ -import os from hub.exports.db_factory import DBFactory as CityExportFactory from hub.imports.db_factory import DBFactory -from hub.imports.user_factory import UserFactory -from hub.exports.user_factory import UserFactory as ExUserFactory +import os +import pickle class Config: def __init__(self): - db_name = None - app_env = None - if os.getenv("FLASK_DEBUG") == 'production': - db_name = 'hub_prod' - app_env = 'PROD' - elif os.getenv("FLASK_DEBUG") == 'testing': - db_name = 'hub_test' - app_env = 'TEST' - - self.export_db_factory = CityExportFactory(db_name=db_name, app_env=app_env, + self.export_db_factory = CityExportFactory(db_name='hub_prod', app_env='PROD', dotenv_path="{}/.env".format(os.path.expanduser('~'))) - self.import_db_factory = DBFactory(db_name=db_name, app_env=app_env, + self.import_db_factory = DBFactory(db_name='hub_prod', app_env='PROD', dotenv_path="{}/.env".format(os.path.expanduser('~'))) - self.user_factory = UserFactory(db_name=db_name, app_env=app_env, - dotenv_path="{}/.env".format(os.path.expanduser('~'))) - self.ex_user_factory = ExUserFactory(db_name=db_name, app_env=app_env, - dotenv_path="{}/.env".format(os.path.expanduser('~'))) def get_city(self, city_id): - return self.export_db_factory.get_city(city_id) - - def get_city_by_name(self, city_name): - return self.export_db_factory.get_city_by_name(city_name) - - def get_city_by_user(self, user_id): - return self.export_db_factory.get_city_by_user(user_id) - - + city_obj = self.export_db_factory.get_city(city_id) + city = pickle.loads(city_obj.city) + for building in city.buildings: + building.heated = True + building.cooled = True + building.attic_heated = 0 + building.basement_heated = 0 + for surface in building.surfaces: + surface.swr = 0.2 + return city diff --git a/hub_api/docs/openapi-specs.yml b/hub_api/docs/openapi-specs.yml index 021ec9b..45fdd9e 100644 --- a/hub_api/docs/openapi-specs.yml +++ b/hub_api/docs/openapi-specs.yml @@ -3,7 +3,7 @@ info: description: NextGen Cities Institute API termsOfService: http://swagger.io/terms/ contact: - email: nextgen-cities@gmail.com + email: peteryefi@gmail.com version: 1.4 externalDocs: description: Find out more about Swagger @@ -11,18 +11,13 @@ externalDocs: paths: /v1.4/uptime: get: + parameters: + [] tags: - Uptime summary: API uptime operationId: uptime description: Retrieve current API uptime - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the Id of the application access this API responses: '200': description: Request sucessful @@ -30,410 +25,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Uptime' - - /v1.4/city: - post: - tags: - - city - summary: Create a city - operationId: createCity - description: Create a new city with a file upload - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the Id of the application access this API - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - city_file: - type: string - format: binary - required: true - responses: - '201': - description: City created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/City' - '200': - description: City not created - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '404': - description: City not found - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - BearerAuth: [] - /v1.4/city/{city_id}: - get: - tags: - - city - summary: Get a city - operationId: getCity - description: Retrieve a city with a given city ID - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the ID of the application access this API - - in: path - name: city_id - schema: - type: integer - required: true - description: Numeric ID of the city to get - responses: - '200': - description: City retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/City' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '404': - description: City not found - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - put: - tags: - - city - summary: Update a city - operationId: updateCity - description: Create a new city with a file upload - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the Id of the application access this API - - in: path - name: city_id - schema: - type: integer - required: true - description: Numeric ID of the city to update - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - city_file: - type: string - format: binary - required: true - responses: - '201': - description: City updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/City' - '200': - description: City not updated - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '404': - description: City not found - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - BearerAuth: [ ] - delete: - tags: - - city - summary: Delete a city - operationId: deleteCity - description: Delete city with specified city_id - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the ID of the application access this API - - in: path - name: city_id - schema: - type: integer - required: true - description: Numeric ID of the city to delete - responses: - '201': - description: City deleted successfully - content: - application/json: - schema: - $ref: '#/components/schemas/City' - '200': - description: City not deleted - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '404': - description: City not found - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - BearerAuth: [ ] - /v1.4/user: - post: - tags: - - user - summary: Create user - description: This can only be done by the logged in admin. - operationId: createUser - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the Id of the application access this API - requestBody: - description: Created user object - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - responses: - '201': - description: User created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/User' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - BearerAuth: [ ] - put: - tags: - - user - summary: Update user - description: This can only be done by the logged in admin. - operationId: updateUser - requestBody: - description: Update user object - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - responses: - '201': - description: User updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/User' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '403': - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - BearerAuth: [ ] - /v1.4/user/login: - post: - tags: - - user - summary: Logs user into the system - description: '' - operationId: loginUser - parameters: - - in: header - name: appId - schema: - type: string - required: true - description: the Id of the application access this API - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Login' - required: true - responses: - '200': - description: Login successful - content: - application/json: - schema: - $ref: '#/components/schemas/LoginRes' - '400': - description: Invalid username/password supplied - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' components: schemas: Uptime: @@ -442,136 +33,4 @@ components: uptime: type: string format: hh:mm:ss.ms - example: "00:09:53.600281" - City: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - name: - type: string - example: Montreal - srs_name: - type: string - example: EPSG:26918 - country: - type: string - example: ca - lon: - type: float - example: 0.38292983 - lat: - type: float - example: 0.92898883 - time_zone: - type: string - example: utc - city_version: - type: integer - format: int64 - example: 1 - lower_corner: - type: array - items: - type: float - example: [610610.7547462888,5035770.347264212,566.5784301757819] - upper_corner: - type: array - items: - type: float - example: [610610.7547462888,5035770.347264212,566.5784301757819] - user: - type: object - $ref: '#/components/schemas/User' - created: - type: string - example: 2023-01-15 18:40:54.64877 - updated: - type: string - example: 2023-01-15 18:40:54.64877 - User: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - name: - type: string - example: Peter Yefi - email: - type: string - format: email - example: peteryefi@gmail.com - password: - type: string - example: 'Hub@183838' - role: - type: string - enum: - - Admin - - Hub_Reader - required: - - name - - email - - password - - role - Login: - type: object - properties: - email: - type: string - example: peteryefi@gmail.com - password: - type: string - example: 'Hub@183838' - required: - - email - - password - LoginRes: - type: object - properties: - token: - type: string - example: eylskdkdjfkdj67uhbnmkhbn908uyhndh - user: - type: object - $ref: '#/components/schemas/User' - ApiResponse: - type: object - properties: - code: - type: integer - format: int32 - message: - type: string - Uptime: - type: object - properties: - uptime: - type: string - requestBodies: - User: - description: User object that is to be created - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - CityArray: - description: List of city object - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/City' - securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT \ No newline at end of file + example: "00:09:53.600281" \ No newline at end of file diff --git a/hub_api/uptime.py b/hub_api/uptime.py index a10c67b..9fabd35 100644 --- a/hub_api/uptime.py +++ b/hub_api/uptime.py @@ -12,8 +12,6 @@ from flask_restful import Resource import hub_api.helpers.session_helper as sh -headers = {'Content-Type': 'application/json'} - class Uptime(Resource): def __init__(self): pass @@ -21,4 +19,4 @@ class Uptime(Resource): @staticmethod def get(): uptime = {"uptime": f"{datetime.datetime.now() - sh.begin_time}"} - return Response(response=json.dumps(uptime), headers=headers) + return Response(json.dumps(uptime)) diff --git a/requirements.txt b/requirements.txt index 5cfd237..521ea31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,5 +24,4 @@ pyecore==0.12.2 jwt==1.3.1 flagger==3.1.0 flasgger -cerc-hub -psycopg2 \ No newline at end of file +cerc-hub \ No newline at end of file