Rebase into previous commit

This commit is contained in:
Guille Gutierrez 2023-02-13 08:09:48 -05:00
parent 1adfe6519f
commit 5f6b7339c4
6 changed files with 71 additions and 718 deletions

View File

@ -19,9 +19,6 @@ from hub_api.greenery_catalog import GreeneryCatalogEntry, GreeneryCatalogEntrie
from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive
from hub_api.uptime import Uptime from hub_api.uptime import Uptime
from hub_api.usage_catalog import UsageCatalogEntry, UsageCatalogEntries, UsageCatalogNames 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() sh.begin_time = datetime.datetime.now()
app = flask.Flask('cerc_api') 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') api.add_resource(UsageCatalogNames, '/v1.4/usage-catalog/names')
# Session # 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(SessionStart, '/v1.4/session/start')
api.add_resource(SessionEnd, '/v1.4/session/end') api.add_resource(SessionEnd, '/v1.4/session/end')
api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive') 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: with open("hub_api/docs/openapi-specs.yml", "r") as stream:
swagger_config = { swagger_config = {

View File

@ -15,147 +15,71 @@ from pathlib import Path
import os import os
from hub_api.config import Config from hub_api.config import Config
headers = {'Content-Type': 'application/json'}
class CityInfo(Resource, Config): class CityInfo(Resource, Config):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@role_required([UserRoles.Admin.value])
def get(self, city_id): 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) 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]) # TODO: this is only for dompark project and need to be removed in future versions.
def put(self, city_id): floor_area = 0
""" wall_construction = 'unknown'
API call to update city in database with new city_file by city_id floor_construction = 'unknown'
:return: json, status code, headers roof_construction = 'unknown'
""" window_type = 'unknown'
allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'} building_dic = {}
try: for building in city.buildings:
city_file = request.files['city_file']
ext = city_file.filename.rsplit('.', 1)[1].lower()
if ext in allowed_ext: usages = [] # This is only valid for dompark project as all the building have the same usage
city_file_type = ext if building.lower_corner[2] == 0:
if ext == 'gml': floor_area += building.floor_area
city_file_type = 'citygml' for internal_zone in building.internal_zones:
elif ext == '3dm': for usage_zone in internal_zone.usage_zones:
city_file_type = 'rhino' usages.append({'percentage': usage_zone.percentage, 'usage': usage_zone.usage})
for thermal_zone in internal_zone.thermal_zones:
file_path = ( for thermal_boundary in thermal_zone.thermal_boundaries:
Path(__file__).parent.parent / 'data/uploaded_city/{}'.format(city_file.filename)).resolve() if thermal_boundary.parent_surface.type == 'Ground':
city_file.save(file_path) floor_construction = thermal_boundary.construction_name
city = GeometryFactory(city_file_type, file_path).city elif thermal_boundary.parent_surface.type == 'Wall':
wall_construction = thermal_boundary.construction_name
saved_city = self.import_db_factory.update_city(city_id, city) for thermal_opening in thermal_boundary.thermal_openings:
if os.path.exists(file_path): if thermal_opening.construction_name is not None:
os.remove(file_path) window_type = thermal_opening.construction_name
if saved_city is None: break
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: else:
return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400) roof_construction = thermal_boundary.construction_name
name = building.human_readable_name
except Exception as err: year_of_construction = str(building.year_of_construction)
logger.error(err) building_dic = {
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while updating city'}), status=400) 'name': str(name),
'floor_area': str(floor_area),
@role_required([UserRoles.Admin.value]) 'year_of_construction': str(year_of_construction),
def delete(self, city_id): 'usages': usages,
""" 'wall_construction': wall_construction,
API call to delete city from database by city_id 'floor_construction': floor_construction,
:return: json, status code, headers 'roof_construction': roof_construction,
""" 'window_type': window_type,
try: 'default_archetype': 'industry ASHRAE_2004:4A non_standard_dompark'
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
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): class City(Resource, Config):
def __init__(self):
super().__init__()
@role_required([UserRoles.Admin.value]) @role_required([UserRoles.Admin.value])
def post(self): 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'} allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'}
try: try:
city_file = request.files['city_file'] 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, 'user': {'id': saved_city.user.id, 'name': saved_city.user.name, 'email': saved_city.user.email,
'role': saved_city.user.role.value} 'role': saved_city.user.role.value}
}, default=str), status=201) }, 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: 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: except Exception as err:
logger.error(err) logger.error(err)
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}), status=400)
status=400, headers=headers)

View File

@ -3,41 +3,28 @@ Config
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Project Peter Yefi peteryefi@gmail.com Copyright © 2023 Project Peter Yefi peteryefi@gmail.com
""" """
import os
from hub.exports.db_factory import DBFactory as CityExportFactory from hub.exports.db_factory import DBFactory as CityExportFactory
from hub.imports.db_factory import DBFactory from hub.imports.db_factory import DBFactory
from hub.imports.user_factory import UserFactory import os
from hub.exports.user_factory import UserFactory as ExUserFactory import pickle
class Config: class Config:
def __init__(self): def __init__(self):
db_name = None self.export_db_factory = CityExportFactory(db_name='hub_prod', app_env='PROD',
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,
dotenv_path="{}/.env".format(os.path.expanduser('~'))) 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('~'))) dotenv_path="{}/.env".format(os.path.expanduser('~')))
def get_city(self, city_id): def get_city(self, city_id):
return self.export_db_factory.get_city(city_id) city_obj = self.export_db_factory.get_city(city_id)
city = pickle.loads(city_obj.city)
def get_city_by_name(self, city_name): for building in city.buildings:
return self.export_db_factory.get_city_by_name(city_name) building.heated = True
building.cooled = True
def get_city_by_user(self, user_id): building.attic_heated = 0
return self.export_db_factory.get_city_by_user(user_id) building.basement_heated = 0
for surface in building.surfaces:
surface.swr = 0.2
return city

View File

@ -3,7 +3,7 @@ info:
description: NextGen Cities Institute API description: NextGen Cities Institute API
termsOfService: http://swagger.io/terms/ termsOfService: http://swagger.io/terms/
contact: contact:
email: nextgen-cities@gmail.com email: peteryefi@gmail.com
version: 1.4 version: 1.4
externalDocs: externalDocs:
description: Find out more about Swagger description: Find out more about Swagger
@ -11,18 +11,13 @@ externalDocs:
paths: paths:
/v1.4/uptime: /v1.4/uptime:
get: get:
parameters:
[]
tags: tags:
- Uptime - Uptime
summary: API uptime summary: API uptime
operationId: uptime operationId: uptime
description: Retrieve current API 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: responses:
'200': '200':
description: Request sucessful description: Request sucessful
@ -30,410 +25,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Uptime' $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: components:
schemas: schemas:
Uptime: Uptime:
@ -443,135 +34,3 @@ components:
type: string type: string
format: hh:mm:ss.ms format: hh:mm:ss.ms
example: "00:09:53.600281" 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

View File

@ -12,8 +12,6 @@ from flask_restful import Resource
import hub_api.helpers.session_helper as sh import hub_api.helpers.session_helper as sh
headers = {'Content-Type': 'application/json'}
class Uptime(Resource): class Uptime(Resource):
def __init__(self): def __init__(self):
pass pass
@ -21,4 +19,4 @@ class Uptime(Resource):
@staticmethod @staticmethod
def get(): def get():
uptime = {"uptime": f"{datetime.datetime.now() - sh.begin_time}"} uptime = {"uptime": f"{datetime.datetime.now() - sh.begin_time}"}
return Response(response=json.dumps(uptime), headers=headers) return Response(json.dumps(uptime))

View File

@ -25,4 +25,3 @@ jwt==1.3.1
flagger==3.1.0 flagger==3.1.0
flasgger flasgger
cerc-hub cerc-hub
psycopg2