fix: 🐛 fixed driver and vehicle apis, swapped dbms to postgres and refactored project structure

This commit is contained in:
steev 2025-01-08 22:42:09 +01:00
parent b40f4a0d02
commit d585c90a4e
12 changed files with 280 additions and 215 deletions

View File

@ -1,8 +0,0 @@
from flask import send_from_directory, request
def serve_vue_app(app):
return send_from_directory(app.static_folder, 'index.html')
def upload_file(request):
file = request.files['file']

171
app.py
View File

@ -1,155 +1,204 @@
from driverHandler import DriverHandler import sys
from gpxInterpreter import GPXHandler import os
from vehicleHandler import VehicleHandler import logging
from flask import Flask, jsonify, send_from_directory, request
from dotenv import load_dotenv from dotenv import load_dotenv
from flask import Flask, app, send_from_directory, request
from sqlalchemy import NullPool, create_engine
from sqlalchemy.orm import sessionmaker, Session from sqlalchemy.orm import sessionmaker, Session
from geoObjects import createTables from modules.driverHandler import DriverHandler
from modules.gpxInterpreter import GPXHandler
from modules.vehicleHandler import VehicleHandler
from modules.geoObjects import create_table, db_connect
from flask_cors import CORS, cross_origin
__session: Session root = logging.getLogger()
FlaskApp: app root.setLevel(logging.DEBUG)
__gpxHandler: GPXHandler
__driverHandler: DriverHandler handler = logging.StreamHandler(sys.stdout)
__vehicleHandler: VehicleHandler handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
print("load environment")
load_dotenv()
def entryPoint(): print("create flask application")
print("load environment") app = Flask(__name__, static_folder='web/dist')
load_dotenv() cors = CORS(app) # allow CORS for all domains on all routes.
app.config['CORS_HEADERS'] = 'Content-Type'
app.logger.setLevel(logging.DEBUG)
print("create flask application") print("initialize database")
FlaskApp = Flask(__name__, static_folder='web/dist') engine = db_connect()
SessionFactory = sessionmaker(bind=engine)
session = SessionFactory()
create_table(engine)
def db_connect(connectionPath): print("initialize handlers")
return create_engine(connectionPath, poolclass=NullPool) gpxHandler = GPXHandler(session)
driverHandler = DriverHandler(session)
__session = sessionmaker(bind=db_connect())() vehicleHandler = VehicleHandler(session)
# handler classes
# handles parsing files and interacting with database
__gpxHandler = GPXHandler(__session)
__driverHandler = DriverHandler(__session)
__vehicleHandler = VehicleHandler(__session)
# creating tables
createTables(__session)
app.run(debug=True)
# TODO: move functional parts out to api package if not handled by other classes
@FlaskApp.route('/') @app.route('/')
@cross_origin()
def serve_vue_app(): def serve_vue_app():
return send_from_directory(FlaskApp.static_folder, 'index.html') return send_from_directory(app.static_folder, 'index.html')
@FlaskApp.route("/track", method=['GET'])
@app.route("/track", methods=['GET'])
@cross_origin()
def getTrack(): def getTrack():
if "start" in request.args and "end" in request.args or "track" in request.args: if "start" in request.args and "end" in request.args or "track" in request.args:
if "start" in request.args and "end" in request.args: if "start" in request.args and "end" in request.args:
# get tracks by filter # get tracks by filter
start = request.args["start"]
end = request.args["end"]
try: try:
return __gpxHandler.getTracksInTime(request.args["start"], request.args["end"]), 200 return gpxHandler.getTracksInTime(start, request.args["end"]), 200
except Exception as e: except Exception as e:
app.logger.debug(f"failed to search tracks error {
e} values: start={start}, end={end}")
return f"error {e}", 500 return f"error {e}", 500
elif "track" in request.args: elif "track" in request.args:
# get track by id # get track by id
track = int(request.args["track"])
try: try:
return __gpxHandler.getTrack(int(request.args["track"])), 200 return gpxHandler.getTrack(track), 200
except Exception as e: except Exception as e:
app.logger.debug(f"fetching track {
track} failed with error {e}")
return f"error {e}", 500 return f"error {e}", 500
pass pass
else: else:
try:
# gets all tracks as list # gets all tracks as list
return __gpxHandler.getTracks() return gpxHandler.getTracks()
except Exception as e:
app.logger.debug(f"fetching all tracks failed with error {e}")
return f"error {e}", 500
@FlaskApp.route("/driver", methods=['GET', 'POST'])
@app.route("/driver", methods=['GET', 'POST'])
@cross_origin()
def handleDriverRoute(): def handleDriverRoute():
if request.method == "GET": if request.method == "GET":
if 'driver' in request.args: if 'driver' in request.args:
try: try:
__driverHandler.getVehicle(int(request.args["driver"])) driver = driverHandler.getDriver(int(request.args["driver"]))
# return drivers, 200 return driver, 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting driver failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
else: else:
try: try:
__driverHandler.getVehicles() drivers = driverHandler.getDrivers()
# return drivers, 200 return drivers, 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting drivers failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
elif request.method == "POST": elif request.method == "POST":
if "name" not in request.args:
# grabbing json from request
data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
app.logger.debug(f"no driver name was found in request")
return "missing name", 400 return "missing name", 400
app.logger.debug(f"driver name has passed check {data["name"]}")
# handle creating vehicle # handle creating vehicle
try: try:
__driverHandler.createVehicle(request.args["name"]) app.logger.debug(f"json request name: {data["name"]}")
# TODO: return id, 200 driver = driverHandler.createDriver(data["name"])
return jsonify({"name": driver.name, "id": driver.id}), 200
except Exception as e: except Exception as e:
app.logger.debug(f"creating drivers failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
@FlaskApp.route("/vehicle", methods=['GET', 'POST'])
@app.route("/vehicle", methods=['GET', 'POST'])
@cross_origin()
def handleVehicleRoute(): def handleVehicleRoute():
if request.method == "GET": if request.method == "GET":
if 'vehicle' in request.args: if 'vehicle' in request.args:
app.logger.debug(f"no vehicle id was found in request")
vehicle = int(request.args["vehicle"])
try: try:
__vehicleHandler.getVehicle(int(request.args["driver"])) foundVehicle = vehicleHandler.getVehicle(vehicle)
# return drivers, 200 return foundVehicle, 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting vehicle {vehicle} failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
else: else:
try: try:
__vehicleHandler.getVehicles() foundVehicles = vehicleHandler.getVehicles()
# return drivers, 200 return foundVehicles, 200
except Exception as e: except Exception as e:
app.logger.debug(f"getting all vehicles failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
elif request.method == "POST": elif request.method == "POST":
if "name" not in request.args: data = request.get_json()
app.logger.debug(f"json request payload: {data}")
if "name" not in data:
return "missing name", 400 return "missing name", 400
if "type" not in request.args: name = data["name"]
return "missing type", 400
# handle creating vehicle # handle creating vehicle
try: try:
__vehicleHandler.createVehicle( vehicle = vehicleHandler.createVehicle(name)
request.args["name"], request.args["type"]) return jsonify({"id": vehicle.id, "name": vehicle.name}), 200
except Exception as e: except Exception as e:
app.logger.debug(f"creating vehicle with name {name} failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
@FlaskApp.route('/upload', methods=['POST'])
@app.route('/upload', methods=['POST'])
@cross_origin()
def uploadFile(): def uploadFile():
if 'file' not in request.files: if 'file' not in request.files:
app.logger.debug("no file was found in clients request")
return "no file provided", 400 return "no file provided", 400
if request.args["routeName"] == '': if request.args["routeName"] == '':
return "no routename provided", 400 app.logger.debug("client did not provide any name for uploaded route")
return "no route name provided", 400
file = request.files['file'] file = request.files['file']
if file.filename == '': if file.filename == '':
app.logger.debug("filename was found empty")
return "no file selected", 400 return "no file selected", 400
try: try:
__gpxHandler.parse(file, request.args["routeName"]) app.logger.debug(f"attempting to parse file: {file.name}")
gpxHandler.parse(file, request.args["routeName"])
return "file stored succesfull", 200 return "file stored succesfull", 200
except Exception as e: except Exception as e:
app.logger.debug(f"storing gpx file failed with error {e}")
return "error" + " " + str(e), 500 return "error" + " " + str(e), 500
if __name__ == '__main__': if __name__ == '__main__':
entryPoint() app.run(debug=True)
app.logger.debug("flask app started")

View File

@ -1,46 +0,0 @@
from sqlalchemy.orm import Session
from geoObjects import Driver
class DriverHandler:
__dbSession: Session
def __init__(self, session: Session):
self.__dbSession = session
pass
# handles creating a driver in the database
def createDriver(self, name: str):
# makes sure that a name always is provided
if not name:
raise ValueError("name is empty")
self.__dbSession.add(Driver(name=name))
self.__dbSession.commit()
pass
# handles getting a driver by its id from the database
def getDriver(self, driverID: int):
driver = self.__dbSession.query(Driver).filter_by(id=driverID).first()
return {
"id": driver.id,
"name": driver.name
}
# handles getting all drivers from the database
def getDrivers(self):
drivers = self.__dbSession.query(Driver).all()
driverList = [
{
"id": driver.id,
"name": driver.name
}
# iterates all drivers and appends them to the list
for driver in drivers
]
return driverList

View File

@ -1,62 +0,0 @@
from sqlalchemy import Column, Date, DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.pool import NullPool
from sqlalchemy.orm import relationship
Base = declarative_base()
def getBase():
# i dont know if i ever need this but if i might here it is
return Base
def createTables(engine):
Base.metadata.create_all(engine)
engine.add(Driver(name="default"))
engine.add(Vehicle(name="default"))
engine.commit()
class Track(Base):
__tablename__ = 'track'
id = Column(Integer, primary_key=True, autoincrement=True)
trackName = Column(String(200), nullable=True)
vehicle = Column(Integer, ForeignKey('vehicle.id'), nullable=False, default=1)
driver = Column(Integer, ForeignKey('driver.id'), nullable=False, default=1)
date = Column(Date, nullable=True)
distance = Column(Float, nullable=False, default=0)
speed = Column(Float, nullable=False, default=0)
start = Column(DateTime, nullable=False)
end = Column(DateTime, nullable=False)
waypoints = relationship('Waypoint', backref='track', lazy=True)
driver = relationship('Driver', backref='track')
vehicle = relationship('Vehicle', backref='track')
pass
class Waypoint(Base):
__tablename__ = 'waypoint'
id = Column(Integer, primary_key=True, autoincrement=True)
lat = Column(Float, nullable=False)
lon = Column(Float, nullable=False)
ele = Column(Float, nullable=False)
speed = Column(Float, nullable=True)
time = Column(DateTime, nullable=True)
track = Column(Integer, ForeignKey('track.id'), nullable=False)
pass
class Driver(Base):
__tablename__ = 'driver'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
track = relationship("Track", back_populates='driver')
pass
class Vehicle(Base):
__tablename__ = 'vehicle'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
track = relationship('Track', back_populates='vehicle')
pass

View File

@ -1,27 +1,25 @@
# Use postgres/example user/password credentials
services: services:
db: db:
image: postgres image: postgres
restart: always
ports:
- 5432:5432
# set shared memory limit when using docker-compose
shm_size: 128mb
# or set shared memory limit when deploy via swarm stack
#volumes:
# - type: tmpfs
# target: /dev/shm
# tmpfs:
# size: 134217728 # 128*2^20 bytes = 128Mb
environment: environment:
- POSTGRES_USER=zahlenraten POSTGRES_USER: example
- POSTGRES_PASSWORD=CYrcTzCEKyDtq&N0M POSTGRES_PASSWORD: example
- POSTGRES_DB=game POSTGRES_DB: geotrack
adminer:
image: adminer
restart: always
ports: ports:
- "5432:5432" - 8080:8080
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend
GeoTrack:
build: .
ports:
- "8000:5000"
networks:
- backend
networks:
backend:
volumes:
pgdata:

0
errors/__init__.py Normal file
View File

0
modules/__init__.py Normal file
View File

58
modules/driverHandler.py Normal file
View File

@ -0,0 +1,58 @@
from sqlalchemy.orm import Session
from modules.geoObjects import Driver
class DriverHandler:
dbSession: Session
def __init__(self, session: Session):
self.dbSession = session
pass
# handles creating a driver in the database
def createDriver(self, name: str) -> Driver:
try:
# makes sure that a name always is provided
if not name:
raise ValueError("name is empty")
driver = Driver(name=name)
self.dbSession.add(driver)
self.dbSession.commit()
return driver
except Exception as e:
raise RuntimeError(f"failed to create driver with errors{e}")
# handles getting a driver by its id from the database
def getDriver(self, driverID: int):
try:
driver = self.dbSession.query(Driver).filter_by(id=driverID).first()
return {
"id": driver.id,
"name": driver.name
}
except Exception as e:
raise RuntimeError(f"failed to get driver with errors{e}")
# handles getting all drivers from the database
def getDrivers(self):
try:
drivers = self.dbSession.query(Driver).all()
driverList = [
{
"id": driver.id,
"name": driver.name
}
# iterates all drivers and appends them to the list
for driver in drivers
]
return driverList
except Exception as e:
raise RuntimeError(f"failed to get drivers with errors{e}")

76
modules/geoObjects.py Normal file
View File

@ -0,0 +1,76 @@
from sqlalchemy import create_engine, Column, Table, ForeignKey, Index, UniqueConstraint, MetaData, SmallInteger, Integer, String, Date, DateTime, Float, Boolean, Text, Numeric, DateTime
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.pool import NullPool
from sqlalchemy.exc import OperationalError
Base = declarative_base()
# Funktion zur Herstellung einer Verbindung zur Datenbank
def db_connect():
try:
engine = create_engine("postgresql://example:example@localhost/geotrack", poolclass=NullPool)
# Überprüfe die Verbindung
with engine.connect() as connection:
print("Verbindung zur Datenbank erfolgreich!")
return engine
except OperationalError as e:
print(f"Fehler bei der Verbindung zur Datenbank: {e}")
return None
# Funktion zur Erstellung der Tabellen
def create_table(engine):
try:
if engine is not None:
Base.metadata.create_all(engine)
print("Tabellen wurden erfolgreich erstellt!")
else:
print("Keine Verbindung zur Datenbank verfügbar.")
except Exception as e:
print(f"Fehler bei der Tabellenerstellung: {e}")
class Track(Base):
__tablename__ = 'track'
id = Column(Integer, primary_key=True, autoincrement=True)
trackName = Column(String(200), nullable=True)
vehicle_id = Column(Integer, ForeignKey('vehicle.id'), nullable=False, default=1)
driver_id = Column(Integer, ForeignKey('driver.id'), nullable=False, default=1)
date = Column(Date, nullable=True)
distance = Column(Float, nullable=False, default=0)
speed = Column(Float, nullable=False, default=0)
start = Column(DateTime, nullable=False)
end = Column(DateTime, nullable=False)
# Beziehungen zu anderen Tabellen
waypoints = relationship('Waypoint', backref='track', lazy=True)
driver = relationship('Driver', back_populates='tracks')
vehicle = relationship('Vehicle', back_populates='tracks')
class Waypoint(Base):
__tablename__ = 'waypoint'
id = Column(Integer, primary_key=True, autoincrement=True)
lat = Column(Float, nullable=False)
lon = Column(Float, nullable=False)
ele = Column(Float, nullable=False)
speed = Column(Float, nullable=True)
time = Column(DateTime, nullable=True)
track_id = Column(Integer, ForeignKey('track.id'), nullable=False)
class Driver(Base):
__tablename__ = 'driver'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
# Beziehung zu Track, zurück über 'tracks' auf Track-Seite
tracks = relationship("Track", back_populates='driver')
class Vehicle(Base):
__tablename__ = 'vehicle'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
# Beziehung zu Track, zurück über 'tracks' auf Track-Seite
tracks = relationship('Track', back_populates='vehicle')

View File

@ -6,7 +6,7 @@ from geojson import Feature, LineString
from geopy.distance import geodesic from geopy.distance import geodesic
from errors.NotFoundException import NotFoundError from errors.NotFoundException import NotFoundError
from geoObjects import Track, Waypoint from modules.geoObjects import Track, Waypoint
class GPXHandler: class GPXHandler:
__dbSession: Session __dbSession: Session

View File

@ -1,29 +1,29 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from geoObjects import Vehicle from modules.geoObjects import Vehicle
class VehicleHandler: class VehicleHandler:
__dbSession: Session dbSession: Session
def __init__(self, session:Session): def __init__(self, session:Session):
self.__dbSession = session self.dbSession = session
pass pass
# handles creating a vehicle and storing it in the database # handles creating a vehicle and storing it in the database
def createVehicle(self, name:str, type:str): def createVehicle(self, name:str) -> Vehicle:
if not name: if not name:
raise ValueError("name is empty") raise ValueError("name is empty")
if not type: vehicle = Vehicle(name=name)
raise ValueError("vehicle is empty")
self.__dbSession.add(Vehicle(name, type)) self.dbSession.add(vehicle)
self.__dbSession.commit() self.dbSession.commit()
pass
return vehicle
# handles getting a vehicle identified with its id from the database # handles getting a vehicle identified with its id from the database
def getVehicle(self, vehicleID:int): def getVehicle(self, vehicleID:int):
vehicle = self.__dbSession.query(Vehicle).filter_by(id=vehicleID).first() vehicle = self.dbSession.query(Vehicle).filter_by(id=vehicleID).first()
return { return {
"id": vehicle.id, "id": vehicle.id,
@ -32,7 +32,7 @@ class VehicleHandler:
# handles getting all vehicles from database # handles getting all vehicles from database
def getVehicles(self): def getVehicles(self):
vehicles = self.__dbSession.query(Vehicle).all() vehicles = self.dbSession.query(Vehicle).all()
driverList = [ driverList = [
{ {