Add update, delete, search city API calls and other bug fixes/code refactors
This commit is contained in:
parent
7b6620eca0
commit
347345b343
23
bootstrap.py
23
bootstrap.py
|
@ -4,17 +4,20 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|||
Copyright © 2021 Project Author name guillermo.gutierrezmorote@concordia.ca
|
||||
Project Collaborator name peteryefi@gmail.com
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import hub_api.helpers.session_helper as sh
|
||||
import flask
|
||||
import yaml
|
||||
from flask_restful import Api
|
||||
from hub_api.city_info import CityInfo, City
|
||||
from hub_api.city_info import CityInfo, CitySearch, City
|
||||
from hub_api.session import SessionStart, SessionEnd, KeepSessionAlive
|
||||
from hub_api.uptime import Uptime
|
||||
from hub_api.user import User, UserLogin
|
||||
from flask import Response
|
||||
from hub_api.city_commands import UpdateCity, DeleteCity, ListCities, SearchCity
|
||||
from flasgger import LazyJSONEncoder, LazyString, Swagger
|
||||
#from persistence.db_setup import DBSetup
|
||||
|
||||
import os
|
||||
|
||||
app = flask.Flask('gamification')
|
||||
app.json_encoder = LazyJSONEncoder
|
||||
|
@ -48,18 +51,14 @@ api.add_resource(SessionEnd, '/v1.4/session/end')
|
|||
api.add_resource(KeepSessionAlive, '/v1.4/session/keep_alive')
|
||||
api.add_resource(CityInfo, '/v1.4/city/<int:city_id>')
|
||||
api.add_resource(City, '/v1.4/city')
|
||||
api.add_resource(UpdateCity, '/v1.4/city/update_city')
|
||||
api.add_resource(DeleteCity, '/v1.4/city/delete_city')
|
||||
api.add_resource(ListCities, '/v1.4/city/list_cities')
|
||||
api.add_resource(SearchCity, '/v1.4/city/search_city')
|
||||
api.add_resource(Greenery, '/v1.4/greenery')
|
||||
|
||||
#api.add_resource(Greenery, '/v1.4/greenery')
|
||||
|
||||
sh.begin_time = datetime.datetime.now()
|
||||
@app.route("/")
|
||||
def home():
|
||||
return Response(headers={'Access-Control-Allow-Origin': '*'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=15789, host="0.0.0.0", debug=False)
|
||||
>>>>>>> main
|
||||
|
||||
#DBSetup(db_name='hub_test', app_env='TEST', dotenv_path=r"C:\Users\k_ls\.env")
|
||||
app.run(port=15789, host="0.0.0.0", debug=True)
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
"""
|
||||
City Commands
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2023 Project Author Koa Wells kekoa.wells@concordia.ca
|
||||
"""
|
||||
|
||||
import json
|
||||
from flask import Response, request
|
||||
from flask_restful import Resource
|
||||
from hub_api import session
|
||||
from hub_api.helpers.session_helper import refresh_session
|
||||
from hub_api.helpers.auth import role_required
|
||||
from imports.db_factory import DBFactory
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
from hub_logger import logger
|
||||
from persistence.models import UserRoles
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
# Admin user commands
|
||||
|
||||
class UpdateCity(Resource):
|
||||
"""
|
||||
UpdateCity class performs an API call to update a city that already exists inside the database
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_factory = DBFactory(db_name='hub_prod', app_env='PROD',
|
||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||
|
||||
@role_required([UserRoles.Admin.value])
|
||||
def put(self, city_id):
|
||||
headers = session.headers
|
||||
|
||||
allowed_ext = {'gml', '3dm', 'xml', 'obj', 'rhino'}
|
||||
try:
|
||||
city_file = request.files['city_file']
|
||||
ext = city_file.filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
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.db_factory.update_city(city_id, city)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
if type(saved_city) is not dict:
|
||||
return Response(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)
|
||||
|
||||
class DeleteCity(Resource):
|
||||
"""
|
||||
DeleteCity class performs an admin API call to delete an existing city stored in the database
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_factory = DBFactory(db_name='hub_prod', app_env='PROD',
|
||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||
|
||||
@role_required([UserRoles.Admin.value])
|
||||
def delete(self):
|
||||
try:
|
||||
payload = request.get_json()
|
||||
return Response(json.dumps(response=DBFactory.delete_city(city_id=payload["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)
|
||||
|
||||
|
||||
# Standard user commands
|
||||
|
||||
class ListCities(Resource):
|
||||
"""
|
||||
ListCities class performs a standard API call to list all existing cities stored in the database
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_factory = DBFactory(db_name='hub_prod', app_env='PROD',
|
||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||
|
||||
def get(self):
|
||||
pass
|
||||
|
||||
class SearchCity(Resource):
|
||||
"""
|
||||
SearchCity class performs a standard API call to select an existing city stored in the database
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_factory = DBFactory(db_name='hub_prod', app_env='PROD',
|
||||
dotenv_path="{}/.env".format(os.path.expanduser('~')))
|
||||
|
||||
def get(self, city_id):
|
||||
pass
|
|
@ -22,11 +22,13 @@ 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)
|
||||
print(city.name)
|
||||
if city:
|
||||
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,
|
||||
|
@ -37,14 +39,123 @@ class CityInfo(Resource, Config):
|
|||
}, 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()
|
||||
|
||||
class City(Resource, Config):
|
||||
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
|
||||
|
||||
|
||||
|
||||
class City(Resource, Config):
|
||||
|
||||
|
||||
@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']
|
||||
|
@ -78,4 +189,5 @@ class City(Resource, Config):
|
|||
return Response(response=json.dumps({'err_msg': 'Unknown city file type'}), status=400, headers=headers)
|
||||
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)
|
||||
return Response(response=json.dumps({'err_msg': 'Sorry an error occurred while creating city'}),
|
||||
status=400, headers=headers)
|
|
@ -3,9 +3,9 @@ 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
|
||||
import os
|
||||
from hub.imports.user_factory import UserFactory
|
||||
from hub.exports.user_factory import UserFactory as ExUserFactory
|
||||
|
||||
|
@ -19,7 +19,7 @@ class Config:
|
|||
db_name = 'hub_prod'
|
||||
app_env = 'PROD'
|
||||
elif os.getenv("FLASK_DEBUG") == 'testing':
|
||||
db_name = 'test_db'
|
||||
db_name = 'hub_test'
|
||||
app_env = 'TEST'
|
||||
|
||||
self.export_db_factory = CityExportFactory(db_name=db_name, app_env=app_env,
|
||||
|
@ -28,9 +28,16 @@ class Config:
|
|||
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)
|
||||
|
||||
|
||||
|
|
|
@ -135,6 +135,142 @@ paths:
|
|||
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:
|
||||
|
@ -382,6 +518,11 @@ components:
|
|||
format: int32
|
||||
message:
|
||||
type: string
|
||||
Uptime:
|
||||
type: object
|
||||
properties:
|
||||
uptime:
|
||||
type: string
|
||||
requestBodies:
|
||||
User:
|
||||
description: User object that is to be created
|
||||
|
|
|
@ -11,6 +11,7 @@ from flask_restful import Resource
|
|||
|
||||
import hub_api.helpers.session_helper as sh
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
class Uptime(Resource):
|
||||
def __init__(self):
|
||||
|
@ -19,4 +20,4 @@ class Uptime(Resource):
|
|||
@staticmethod
|
||||
def get():
|
||||
uptime = {"uptime": f"{datetime.datetime.now() - sh.begin_time}"}
|
||||
return Response(json.dumps(uptime))
|
||||
return Response(response=json.dumps(uptime), headers=headers)
|
||||
|
|
|
@ -24,3 +24,4 @@ pyecore==0.12.2
|
|||
jwt==1.3.1
|
||||
flagger==3.1.0
|
||||
flasgger
|
||||
psycopg2
|
Loading…
Reference in New Issue
Block a user