| @@ -33,7 +33,19 @@ OR | |||||
| ``` | ``` | ||||
| python3 -m uvicorn main:app --reload | python3 -m uvicorn main:app --reload | ||||
| ``` | ``` | ||||
| ## Explaining the file structure | |||||
| ``` | |||||
| app //main app | |||||
| |-alembic //Alembic -- database migration manager. Helps keep databases in sync and changeable (handles all SQL logic and keeps everything up-to-date) | |||||
| | |- //All files irrelevant | |||||
| |-apis //Stores the actual API Endpoints. | |||||
| | |-v1 //Main API endpoints | |||||
| |-core //Core project components | |||||
| |-db //Stores database information and data | |||||
| | |-models //Stores database models (represents SQL Tables) | |||||
| | |-repository //Stores scripts to create, update and interact with the database | |||||
| |-schemas //Stores Pydantic data models (Kind of like db/models except for use in the API itself, not just the database. Uses for data validation and stuff) | |||||
| ``` | |||||
| ## Git Guide | ## Git Guide | ||||
| - [Git Reference](http://git.github.io/git-reference): despite its name it is more of a user guide. | - [Git Reference](http://git.github.io/git-reference): despite its name it is more of a user guide. | ||||
| - ["Pro Git" Book](http://git-scm.com/book): by a core developer, also a thorough user guide. | - ["Pro Git" Book](http://git-scm.com/book): by a core developer, also a thorough user guide. | ||||
| @@ -0,0 +1,116 @@ | |||||
| # A generic, single database configuration. | |||||
| [alembic] | |||||
| # path to migration scripts | |||||
| script_location = alembic | |||||
| # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s | |||||
| # Uncomment the line below if you want the files to be prepended with date and time | |||||
| # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file | |||||
| # for all available tokens | |||||
| # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s | |||||
| # sys.path path, will be prepended to sys.path if present. | |||||
| # defaults to the current working directory. | |||||
| prepend_sys_path = . | |||||
| # timezone to use when rendering the date within the migration file | |||||
| # as well as the filename. | |||||
| # If specified, requires the python-dateutil library that can be | |||||
| # installed by adding `alembic[tz]` to the pip requirements | |||||
| # string value is passed to dateutil.tz.gettz() | |||||
| # leave blank for localtime | |||||
| # timezone = | |||||
| # max length of characters to apply to the | |||||
| # "slug" field | |||||
| # truncate_slug_length = 40 | |||||
| # set to 'true' to run the environment during | |||||
| # the 'revision' command, regardless of autogenerate | |||||
| # revision_environment = false | |||||
| # set to 'true' to allow .pyc and .pyo files without | |||||
| # a source .py file to be detected as revisions in the | |||||
| # versions/ directory | |||||
| # sourceless = false | |||||
| # version location specification; This defaults | |||||
| # to alembic/versions. When using multiple version | |||||
| # directories, initial revisions must be specified with --version-path. | |||||
| # The path separator used here should be the separator specified by "version_path_separator" below. | |||||
| # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions | |||||
| # version path separator; As mentioned above, this is the character used to split | |||||
| # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. | |||||
| # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. | |||||
| # Valid values for version_path_separator are: | |||||
| # | |||||
| # version_path_separator = : | |||||
| # version_path_separator = ; | |||||
| # version_path_separator = space | |||||
| version_path_separator = os # Use os.pathsep. Default configuration used for new projects. | |||||
| # set to 'true' to search source files recursively | |||||
| # in each "version_locations" directory | |||||
| # new in Alembic version 1.10 | |||||
| # recursive_version_locations = false | |||||
| # the output encoding used when revision files | |||||
| # are written from script.py.mako | |||||
| # output_encoding = utf-8 | |||||
| sqlalchemy.url = driver://user:pass@localhost/dbname | |||||
| [post_write_hooks] | |||||
| # post_write_hooks defines scripts or Python functions that are run | |||||
| # on newly generated revision scripts. See the documentation for further | |||||
| # detail and examples | |||||
| # format using "black" - use the console_scripts runner, against the "black" entrypoint | |||||
| # hooks = black | |||||
| # black.type = console_scripts | |||||
| # black.entrypoint = black | |||||
| # black.options = -l 79 REVISION_SCRIPT_FILENAME | |||||
| # lint with attempts to fix using "ruff" - use the exec runner, execute a binary | |||||
| # hooks = ruff | |||||
| # ruff.type = exec | |||||
| # ruff.executable = %(here)s/.venv/bin/ruff | |||||
| # ruff.options = --fix REVISION_SCRIPT_FILENAME | |||||
| # Logging configuration | |||||
| [loggers] | |||||
| keys = root,sqlalchemy,alembic | |||||
| [handlers] | |||||
| keys = console | |||||
| [formatters] | |||||
| keys = generic | |||||
| [logger_root] | |||||
| level = WARN | |||||
| handlers = console | |||||
| qualname = | |||||
| [logger_sqlalchemy] | |||||
| level = WARN | |||||
| handlers = | |||||
| qualname = sqlalchemy.engine | |||||
| [logger_alembic] | |||||
| level = INFO | |||||
| handlers = | |||||
| qualname = alembic | |||||
| [handler_console] | |||||
| class = StreamHandler | |||||
| args = (sys.stderr,) | |||||
| level = NOTSET | |||||
| formatter = generic | |||||
| [formatter_generic] | |||||
| format = %(levelname)-5.5s [%(name)s] %(message)s | |||||
| datefmt = %H:%M:%S | |||||
| @@ -0,0 +1 @@ | |||||
| Generic single-database configuration. | |||||
| @@ -0,0 +1,83 @@ | |||||
| from logging.config import fileConfig | |||||
| from sqlalchemy import engine_from_config | |||||
| from sqlalchemy import pool | |||||
| from alembic import context | |||||
| from core.config import settings | |||||
| from db.base import Base | |||||
| # this is the Alembic Config object, which provides | |||||
| # access to the values within the .ini file in use. | |||||
| config = context.config | |||||
| config.set_main_option("sqlalchemy.url", settings.DATABASE_URL) | |||||
| # Interpret the config file for Python logging. | |||||
| # This line sets up loggers basically. | |||||
| if config.config_file_name is not None: | |||||
| fileConfig(config.config_file_name) | |||||
| # add your model's MetaData object here | |||||
| # for 'autogenerate' support | |||||
| # from myapp import mymodel | |||||
| # target_metadata = mymodel.Base.metadata | |||||
| target_metadata = Base.metadata | |||||
| # other values from the config, defined by the needs of env.py, | |||||
| # can be acquired: | |||||
| # my_important_option = config.get_main_option("my_important_option") | |||||
| # ... etc. | |||||
| def run_migrations_offline() -> None: | |||||
| """Run migrations in 'offline' mode. | |||||
| This configures the context with just a URL | |||||
| and not an Engine, though an Engine is acceptable | |||||
| here as well. By skipping the Engine creation | |||||
| we don't even need a DBAPI to be available. | |||||
| Calls to context.execute() here emit the given string to the | |||||
| script output. | |||||
| """ | |||||
| url = config.get_main_option("sqlalchemy.url") | |||||
| context.configure( | |||||
| url=url, | |||||
| target_metadata=target_metadata, | |||||
| literal_binds=True, | |||||
| dialect_opts={"paramstyle": "named"}, | |||||
| ) | |||||
| with context.begin_transaction(): | |||||
| context.run_migrations() | |||||
| def run_migrations_online() -> None: | |||||
| """Run migrations in 'online' mode. | |||||
| In this scenario we need to create an Engine | |||||
| and associate a connection with the context. | |||||
| """ | |||||
| connectable = engine_from_config( | |||||
| config.get_section(config.config_ini_section, {}), | |||||
| prefix="sqlalchemy.", | |||||
| poolclass=pool.NullPool, | |||||
| ) | |||||
| with connectable.connect() as connection: | |||||
| context.configure(connection=connection, target_metadata=target_metadata) | |||||
| with context.begin_transaction(): | |||||
| context.run_migrations() | |||||
| if context.is_offline_mode(): | |||||
| run_migrations_offline() | |||||
| else: | |||||
| run_migrations_online() | |||||
| @@ -0,0 +1,26 @@ | |||||
| """${message} | |||||
| Revision ID: ${up_revision} | |||||
| Revises: ${down_revision | comma,n} | |||||
| Create Date: ${create_date} | |||||
| """ | |||||
| from typing import Sequence, Union | |||||
| from alembic import op | |||||
| import sqlalchemy as sa | |||||
| ${imports if imports else ""} | |||||
| # revision identifiers, used by Alembic. | |||||
| revision: str = ${repr(up_revision)} | |||||
| down_revision: Union[str, None] = ${repr(down_revision)} | |||||
| branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} | |||||
| depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} | |||||
| def upgrade() -> None: | |||||
| ${upgrades if upgrades else "pass"} | |||||
| def downgrade() -> None: | |||||
| ${downgrades if downgrades else "pass"} | |||||
| @@ -0,0 +1,60 @@ | |||||
| """create User and Vehicle tables | |||||
| Revision ID: 9a0214838ac8 | |||||
| Revises: | |||||
| Create Date: 2023-09-01 13:31:01.324861 | |||||
| """ | |||||
| from typing import Sequence, Union | |||||
| from alembic import op | |||||
| import sqlalchemy as sa | |||||
| # revision identifiers, used by Alembic. | |||||
| revision: str = '9a0214838ac8' | |||||
| down_revision: Union[str, None] = None | |||||
| branch_labels: Union[str, Sequence[str], None] = None | |||||
| depends_on: Union[str, Sequence[str], None] = None | |||||
| def upgrade() -> None: | |||||
| # ### commands auto generated by Alembic - please adjust! ### | |||||
| op.create_table('users', | |||||
| sa.Column('Id', sa.Integer(), nullable=False), | |||||
| sa.Column('Name', sa.String(), nullable=False), | |||||
| sa.Column('MiddleName', sa.String(), nullable=True), | |||||
| sa.Column('LastName', sa.String(), nullable=False), | |||||
| sa.Column('BirthDate', sa.DateTime(), nullable=False), | |||||
| sa.Column('ContactNumber', sa.String(), nullable=False), | |||||
| sa.Column('Email', sa.String(), nullable=False), | |||||
| sa.Column('Role', sa.String(), nullable=False), | |||||
| sa.Column('DrivingLicenseNumber', sa.String(), nullable=True), | |||||
| sa.Column('HashedPassword', sa.String(), nullable=False), | |||||
| sa.PrimaryKeyConstraint('Id') | |||||
| ) | |||||
| op.create_index(op.f('ix_users_Id'), 'users', ['Id'], unique=False) | |||||
| op.create_table('vehicles', | |||||
| sa.Column('Id', sa.Integer(), nullable=False), | |||||
| sa.Column('Model', sa.String(), nullable=False), | |||||
| sa.Column('Year', sa.Integer(), nullable=False), | |||||
| sa.Column('LicensePlate', sa.String(), nullable=False), | |||||
| sa.Column('Type', sa.String(), nullable=False), | |||||
| sa.Column('AssignedDriverIds', sa.ARRAY(sa.Integer()), nullable=True), | |||||
| sa.Column('CurrentLocation', sa.ARRAY(sa.String()), nullable=True), | |||||
| sa.Column('Fuel', sa.Integer(), nullable=False), | |||||
| sa.Column('Mileage', sa.Integer(), nullable=False), | |||||
| sa.Column('MaintenanceNotes', sa.ARRAY(sa.String()), nullable=True), | |||||
| sa.PrimaryKeyConstraint('Id') | |||||
| ) | |||||
| op.create_index(op.f('ix_vehicles_Id'), 'vehicles', ['Id'], unique=False) | |||||
| # ### end Alembic commands ### | |||||
| def downgrade() -> None: | |||||
| # ### commands auto generated by Alembic - please adjust! ### | |||||
| op.drop_index(op.f('ix_vehicles_Id'), table_name='vehicles') | |||||
| op.drop_table('vehicles') | |||||
| op.drop_index(op.f('ix_users_Id'), table_name='users') | |||||
| op.drop_table('users') | |||||
| # ### end Alembic commands ### | |||||
| @@ -0,0 +1,8 @@ | |||||
| # Base API router -- collecting all APIs here to not clutter main.py | |||||
| from fastapi import APIRouter | |||||
| from apis.v1 import route_user, route_vehicle | |||||
| api_router = APIRouter() | |||||
| api_router.include_router(route_user.router, prefix="/user", tags=["users"]) | |||||
| api_router.include_router(route_vehicle.router, prefix="/vehicle", tags=["vehicles"]) | |||||
| @@ -0,0 +1,35 @@ | |||||
| # Routes for user. MAIN PART OF THE API | |||||
| from fastapi import APIRouter, HTTPException, status | |||||
| from sqlalchemy.orm import Session | |||||
| from fastapi import Depends | |||||
| from typing import List | |||||
| from schemas.user import UserCreate, ShowUser | |||||
| from db.session import get_db | |||||
| from db.repository.user import create_new_user, list_users, get_user_by_id | |||||
| router = APIRouter() | |||||
| @router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED) | |||||
| def create_user(user: UserCreate, db: Session = Depends(get_db)): | |||||
| user = create_new_user(user=user, db=db) | |||||
| return user | |||||
| @router.get("/", response_model=List[ShowUser], status_code=status.HTTP_200_OK) | |||||
| def get_all_users(db: Session = Depends(get_db), role: str = None): | |||||
| if role == None: | |||||
| users = list_users(db=db) | |||||
| return users | |||||
| users = list_users(db=db, role=role) | |||||
| return users | |||||
| @router.get("/{user_id}", response_model=ShowUser, status_code=status.HTTP_200_OK) | |||||
| def get_user(user_id: int, db: Session = Depends(get_db)): | |||||
| user = get_user_by_id(user_id=user_id, db=db) | |||||
| if not user: | |||||
| raise HTTPException(status_code=404, detail="User not found") | |||||
| return user | |||||
| @@ -0,0 +1,95 @@ | |||||
| from fastapi import APIRouter, status, HTTPException | |||||
| from sqlalchemy.orm import Session | |||||
| from fastapi import Depends | |||||
| from typing import List | |||||
| from db.session import get_db | |||||
| from schemas.vehicle import OutputVehicle, CreateVehicle, UpdateVehicle | |||||
| from db.repository.vehicle import ( | |||||
| create_new_vehicle, | |||||
| assign_vehicle_driver, | |||||
| list_vehicles, | |||||
| get_vehicle_by_id, | |||||
| replace_vehicle_data, | |||||
| ) | |||||
| router = APIRouter() | |||||
| @router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED) | |||||
| async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db)): | |||||
| vehicle = create_new_vehicle(vehicle=vehicle, db=db) | |||||
| return vehicle | |||||
| # @router.get( | |||||
| # "/assign/{vehicle_id}/{driver_id}", | |||||
| # response_model=OutputVehicle, | |||||
| # status_code=status.HTTP_200_OK, | |||||
| # ) | |||||
| # async def assign_drver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db)): | |||||
| # vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | |||||
| # if vehicle == "nodriver": | |||||
| # raise HTTPException( | |||||
| # status_code=404, detail=f"Driver with id {driver_id} not found" | |||||
| # ) | |||||
| # if vehicle == "novehicle": | |||||
| # raise HTTPException( | |||||
| # status_code=404, detail=f"Vehicle with id {vehicle_id} not found" | |||||
| # ) | |||||
| # if vehicle == "alreadyassigned": | |||||
| # raise HTTPException( | |||||
| # status_code=400, | |||||
| # detail=f"Driver with id {driver_id} is already assigned to vehicle with id {vehicle_id}", | |||||
| # ) | |||||
| # return vehicle | |||||
| @router.patch( | |||||
| "/{vehicle_id}/driver/{driver_id}", | |||||
| response_model=OutputVehicle, | |||||
| status_code=status.HTTP_200_OK, | |||||
| ) | |||||
| async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db)): | |||||
| vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | |||||
| if vehicle == "nodriver": | |||||
| raise HTTPException( | |||||
| status_code=404, detail=f"Driver with id {driver_id} not found" | |||||
| ) | |||||
| if vehicle == "novehicle": | |||||
| raise HTTPException( | |||||
| status_code=404, detail=f"Vehicle with id {vehicle_id} not found" | |||||
| ) | |||||
| if vehicle == "alreadyassigned": | |||||
| raise HTTPException( | |||||
| status_code=400, | |||||
| detail=f"Driver with id {driver_id} is already assigned to vehicle with id {vehicle_id}", | |||||
| ) | |||||
| return vehicle | |||||
| @router.get("/", response_model=List[OutputVehicle], status_code=status.HTTP_200_OK) | |||||
| async def get_all_vehicles(db: Session = Depends(get_db)): | |||||
| vehicles = list_vehicles(db=db) | |||||
| return vehicles | |||||
| @router.get( | |||||
| "/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | |||||
| ) | |||||
| async def get_vehicle(vehicle_id: int, db: Session = Depends(get_db)): | |||||
| vehicle = get_vehicle_by_id(vehicle_id=vehicle_id, db=db) | |||||
| if not vehicle: | |||||
| raise HTTPException(status_code=404, detail="Vehicle not found") | |||||
| return vehicle | |||||
| @router.put( | |||||
| "/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | |||||
| ) | |||||
| def update_vehicle( | |||||
| vehicle_id: int, vehicle: UpdateVehicle, db: Session = Depends(get_db) | |||||
| ): | |||||
| vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db) | |||||
| if vehicleRes == "vehicleNotFound": | |||||
| raise HTTPException(status_code=404, detail="Vehicle not found") | |||||
| elif vehicleRes == "badreq": | |||||
| raise HTTPException(status_code=502, detail="Bad request") | |||||
| return vehicleRes | |||||
| @@ -0,0 +1,14 @@ | |||||
| # Hashing functions for passwords | |||||
| from passlib.context import CryptContext | |||||
| pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") | |||||
| class Hasher: | |||||
| @staticmethod | |||||
| def verify_password(plain_password, hashed_password): | |||||
| return pwd_context.verify(plain_password, hashed_password) | |||||
| @staticmethod | |||||
| def get_password_hash(plain_password): | |||||
| return pwd_context.hash(plain_password) | |||||
| @@ -0,0 +1,6 @@ | |||||
| # Base class for all sqlalchemy models | |||||
| from sqlalchemy.ext.declarative import declarative_base | |||||
| Base = declarative_base() | |||||
| from db.models.user import User | |||||
| from db.models.vehicle import Vehicle | |||||
| @@ -1,11 +0,0 @@ | |||||
| from typing import Any | |||||
| from sqlalchemy.ext.declarative import declared_attr | |||||
| from sqlalchemy.orm import as_declarative | |||||
| @as_declarative() | |||||
| class Base: | |||||
| id: Any | |||||
| __name__: str | |||||
| def __tablename__(cls) -> str: | |||||
| return cls.__name__.lower() | |||||
| @@ -0,0 +1,18 @@ | |||||
| # PostgreSQL table model for users | |||||
| from sqlalchemy import Column, Integer, String, DateTime, Boolean, URL | |||||
| from sqlalchemy.orm import relationship | |||||
| from db.base import Base | |||||
| class User(Base): | |||||
| __tablename__ = "users" | |||||
| Id = Column(Integer, primary_key=True, index=True) | |||||
| Name = Column(String, nullable=False) | |||||
| MiddleName = Column(String, nullable=True) | |||||
| LastName = Column(String, nullable=False) | |||||
| BirthDate = Column(DateTime, nullable=False) | |||||
| ContactNumber = Column(String, nullable=False) | |||||
| Email = Column(String, nullable=False) | |||||
| Role = Column(String, nullable=False) | |||||
| DrivingLicenseNumber = Column(String, nullable=True) | |||||
| HashedPassword = Column(String, nullable=False) | |||||
| @@ -0,0 +1,27 @@ | |||||
| # Postgres table model for vehicles | |||||
| from sqlalchemy import ( | |||||
| Column, | |||||
| Integer, | |||||
| String, | |||||
| DateTime, | |||||
| Boolean, | |||||
| URL, | |||||
| ARRAY, | |||||
| ForeignKey, | |||||
| ) | |||||
| from sqlalchemy.orm import relationship | |||||
| from db.base import Base | |||||
| class Vehicle(Base): | |||||
| __tablename__ = "vehicles" | |||||
| Id = Column(Integer, primary_key=True, index=True) | |||||
| Model = Column(String, nullable=False) | |||||
| Year = Column(Integer, nullable=False) | |||||
| LicensePlate = Column(String, nullable=False) | |||||
| Type = Column(String, nullable=False) | |||||
| AssignedDriverIds = Column(ARRAY(Integer), nullable=True) | |||||
| CurrentLocation = Column(ARRAY(String), nullable=True) | |||||
| Fuel = Column(Integer, nullable=False) | |||||
| Mileage = Column(Integer, nullable=False) | |||||
| MaintenanceNotes = Column(ARRAY(String), nullable=True) | |||||
| @@ -0,0 +1,42 @@ | |||||
| # Creating a new user in the database | |||||
| from sqlalchemy.orm import Session | |||||
| from schemas.user import UserCreate | |||||
| from db.models.user import User | |||||
| from core.hashing import Hasher | |||||
| def create_new_user(user: UserCreate, db: Session): | |||||
| user_object = User( | |||||
| Email=user.Email, | |||||
| Name=user.Name, | |||||
| MiddleName=user.MiddleName, | |||||
| LastName=user.LastName, | |||||
| BirthDate=user.BirthDate, | |||||
| ContactNumber=user.ContactNumber, | |||||
| Role=user.Role, | |||||
| HashedPassword=Hasher.get_password_hash(user.Password), | |||||
| ) | |||||
| db.add(user_object) | |||||
| db.commit() | |||||
| db.refresh(user_object) | |||||
| return user_object | |||||
| def get_user_by_id(user_id: int, db: Session): | |||||
| user = db.query(User).filter(User.Id == user_id).first() | |||||
| return user | |||||
| def verify_driver_exists(driver_id: int, db: Session): | |||||
| driver = db.query(User).filter(User.Id == driver_id).first() | |||||
| if not driver: | |||||
| return False | |||||
| if driver.Role != "Driver": | |||||
| return False | |||||
| return True | |||||
| def list_users(db: Session, role: str = "Any"): | |||||
| users = db.query(User).filter((User.Role == role) | (role == "Any")).all() | |||||
| return users | |||||
| @@ -0,0 +1,67 @@ | |||||
| from sqlalchemy.orm import Session | |||||
| from schemas.vehicle import CreateVehicle, OutputVehicle, UpdateVehicle | |||||
| from db.models.vehicle import Vehicle | |||||
| from db.repository.user import verify_driver_exists | |||||
| def create_new_vehicle(vehicle: CreateVehicle, db: Session): | |||||
| vehicle_object = Vehicle( | |||||
| **vehicle.model_dump(), | |||||
| Fuel=0, | |||||
| AssignedDriverIds=[], | |||||
| CurrentLocation=[], | |||||
| MaintenanceNotes=[] | |||||
| ) | |||||
| db.add(vehicle_object) | |||||
| db.commit() | |||||
| db.refresh(vehicle_object) | |||||
| return vehicle | |||||
| def assign_vehicle_driver(vehicle_id: int, driver_id: int, db: Session): | |||||
| vehicledb = db.query(Vehicle).filter(Vehicle.Id == vehicle_id) | |||||
| vehicle = vehicledb.first() | |||||
| if not vehicle: | |||||
| return "novehicle" | |||||
| if driver_id in vehicle.AssignedDriverIds: | |||||
| return "alreadyassigned" | |||||
| if verify_driver_exists(driver_id=driver_id, db=db): | |||||
| print(vehicle.AssignedDriverIds) | |||||
| vehicledb.update({"AssignedDriverIds": vehicle.AssignedDriverIds + [driver_id]}) | |||||
| print(vehicle.AssignedDriverIds) | |||||
| db.add(vehicle) | |||||
| db.commit() | |||||
| db.refresh(vehicle) | |||||
| return vehicle | |||||
| # return a 404 error if the driver does not exist | |||||
| return "nodriver" | |||||
| def list_vehicles(db: Session): | |||||
| vehicles = db.query(Vehicle).all() | |||||
| return vehicles | |||||
| def get_vehicle_by_id(vehicle_id: int, db: Session): | |||||
| vehicle = db.query(Vehicle).filter(Vehicle.Id == vehicle_id).first() | |||||
| return vehicle | |||||
| def replace_vehicle_data(id: int, vehicle: UpdateVehicle, db: Session): | |||||
| vehicle_db = db.query(Vehicle).filter(Vehicle.Id == id) | |||||
| vehicle_object = vehicle_db.first() | |||||
| if not vehicle_object: | |||||
| return "vehiclenotfound" | |||||
| vehicle_object.AssignedDriverIds = vehicle.AssignedDriverIds | |||||
| vehicle_object.CurrentLocation = vehicle.CurrentLocation | |||||
| vehicle_object.Fuel = vehicle.Fuel | |||||
| vehicle_object.LicensePlate = vehicle.LicensePlate | |||||
| vehicle_object.MaintenanceNotes = vehicle.MaintenanceNotes | |||||
| vehicle_object.Mileage = vehicle.Mileage | |||||
| vehicle_object.Model = vehicle.Model | |||||
| vehicle_object.Type = vehicle.Type | |||||
| vehicle_object.Year = vehicle.Year | |||||
| print(vehicle_object) | |||||
| db.add(vehicle_object) | |||||
| db.commit() | |||||
| return vehicle_object | |||||
| @@ -1,11 +1,21 @@ | |||||
| # Information about the database session is stored here | |||||
| from sqlalchemy import create_engine | from sqlalchemy import create_engine | ||||
| from sqlalchemy.orm import sessionmaker | from sqlalchemy.orm import sessionmaker | ||||
| from core.config import settings | |||||
| from core.config import settings | |||||
| SQLALCHEMY_DATABASE_URL = settings.DATABASE_URL | |||||
| SQLALCHEMY_DATABASE_URL = ( | |||||
| settings.DATABASE_URL | |||||
| ) # get the database url from core/config.py | |||||
| engine = create_engine(SQLALCHEMY_DATABASE_URL) | engine = create_engine(SQLALCHEMY_DATABASE_URL) | ||||
| print(SQLALCHEMY_DATABASE_URL) | print(SQLALCHEMY_DATABASE_URL) | ||||
| SessionLocal = sessionmaker(autocommit=False,autoflush=False,bind=engine) | |||||
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | |||||
| def get_db(): # get the database session (if we change the database, we can change it here) | |||||
| db = SessionLocal() | |||||
| try: | |||||
| yield db | |||||
| finally: | |||||
| db.close() | |||||
| @@ -1,25 +1,30 @@ | |||||
| from fastapi import FastAPI | from fastapi import FastAPI | ||||
| from core.config import settings | from core.config import settings | ||||
| from db.session import engine | from db.session import engine | ||||
| from db.base_model import Base | |||||
| from db.base import Base | |||||
| from apis.base import api_router | |||||
| def startup(): #start the project, and create the tables | |||||
| app = FastAPI(title=settings.PROJECT_NAME,version=settings.PROJECT_VERSION) | |||||
| def include_routes(app): # include all routes from our api/v1/ | |||||
| app.include_router(api_router) | |||||
| def startup(): # start the project, and create the tables | |||||
| app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION) | |||||
| Base.metadata.create_all(bind=engine) | Base.metadata.create_all(bind=engine) | ||||
| include_routes(app) | |||||
| return app | return app | ||||
| app = startup() | |||||
| app = startup() | |||||
| # Testing stuff | |||||
| @app.get("/") | @app.get("/") | ||||
| def root(): | def root(): | ||||
| return {"message": "Hello World!"} | return {"message": "Hello World!"} | ||||
| @app.get("/user") | @app.get("/user") | ||||
| def get_users(): | def get_users(): | ||||
| return { | |||||
| {"name": "almaz"}, | |||||
| {"name": "madi"} | |||||
| } | |||||
| return {{"name": "almaz"}, {"name": "madi"}} | |||||
| @@ -0,0 +1,30 @@ | |||||
| # Purpose: User schema for pydantic (validation, inside-api usage) | |||||
| from datetime import datetime | |||||
| from pydantic import BaseModel, EmailStr, Field | |||||
| class UserCreate(BaseModel): | |||||
| Email: EmailStr | |||||
| Password: str = Field(..., min_length=7, max_length=20) | |||||
| Name: str = Field(..., min_length=3, max_length=50) | |||||
| MiddleName: str = Field(None, min_length=3, max_length=50) | |||||
| LastName: str = Field(..., min_length=3, max_length=50) | |||||
| ContactNumber: str = Field(..., min_length=12, max_length=12) | |||||
| BirthDate: datetime = Field(...) | |||||
| Email: EmailStr = Field(...) | |||||
| Role: str = Field(..., min_length=3, max_length=50) | |||||
| class ShowUser(BaseModel): | |||||
| Id: int | |||||
| Name: str | |||||
| MiddleName: str | |||||
| LastName: str | |||||
| ContactNumber: str | |||||
| BirthDate: datetime | |||||
| Email: EmailStr | |||||
| Role: str | |||||
| class Config: | |||||
| orm_mode = True | |||||
| @@ -0,0 +1,37 @@ | |||||
| from typing import Optional | |||||
| from pydantic import BaseModel, root_validator | |||||
| from datetime import datetime | |||||
| class CreateVehicle(BaseModel): | |||||
| Id: int | |||||
| Model: str | |||||
| Year: int | |||||
| LicensePlate: str | |||||
| Type: str | |||||
| Mileage: int | |||||
| class OutputVehicle(BaseModel): | |||||
| Id: int | |||||
| Model: str | |||||
| Year: int | |||||
| LicensePlate: str | |||||
| Type: str | |||||
| Mileage: int | |||||
| CurrentLocation: Optional[list[str]] = None | |||||
| Fuel: Optional[int] = 0 | |||||
| MaintenanceNotes: Optional[list[str]] = None | |||||
| AssignedDriverIds: Optional[list[int]] = None | |||||
| class UpdateVehicle(BaseModel): | |||||
| Model: str | |||||
| Year: int | |||||
| LicensePlate: str | |||||
| Type: str | |||||
| Mileage: int | |||||
| CurrentLocation: Optional[list[str]] = None | |||||
| Fuel: Optional[int] = 0 | |||||
| MaintenanceNotes: Optional[list[str]] = None | |||||
| AssignedDriverIds: Optional[list[int]] = None | |||||
| @@ -1,3 +1,6 @@ | |||||
| fastapi[all] | fastapi[all] | ||||
| pydantic | |||||
| sqlalchemy | sqlalchemy | ||||
| psycopg2 | |||||
| psycopg2 | |||||
| alembic==1.12.0 | |||||
| passlib | |||||