| @@ -14,7 +14,7 @@ | |||||
| ## How to launch | ## How to launch | ||||
| First, make sure that python is installed. Create a virtual environment (if you want to) | First, make sure that python is installed. Create a virtual environment (if you want to) | ||||
| ``` | ``` | ||||
| python -m venv venv | |||||
| python/python3 -m venv venv | |||||
| source /venv/bin/activate | source /venv/bin/activate | ||||
| ``` | ``` | ||||
| (not necessary) | (not necessary) | ||||
| @@ -1 +0,0 @@ | |||||
| Generic single-database configuration. | |||||
| @@ -1,83 +0,0 @@ | |||||
| 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() | |||||
| @@ -1,26 +0,0 @@ | |||||
| """${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"} | |||||
| @@ -1,60 +0,0 @@ | |||||
| """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 ### | |||||
| @@ -1,3 +1,4 @@ | |||||
| from datetime import datetime | |||||
| from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm | from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm | ||||
| from fastapi import Depends, APIRouter | from fastapi import Depends, APIRouter | ||||
| from sqlalchemy.orm import Session | from sqlalchemy.orm import Session | ||||
| @@ -7,20 +8,23 @@ from db.session import get_db | |||||
| from core.hashing import Hasher | from core.hashing import Hasher | ||||
| from core.config import settings | from core.config import settings | ||||
| from jose import JWTError, jwt | from jose import JWTError, jwt | ||||
| from schemas.token import Token | |||||
| from schemas.token import Token, TokenPayload | |||||
| from db.repository.user import get_user_by_email, get_user_by_phone | from db.repository.user import get_user_by_email, get_user_by_phone | ||||
| from core.auth import create_access_token | from core.auth import create_access_token | ||||
| router = APIRouter() | router = APIRouter() | ||||
| oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") | |||||
| oauth2_scheme = OAuth2PasswordBearer( | |||||
| tokenUrl="/token", | |||||
| ) | |||||
| def authenticate_user(login: str, password: str, db: Session): | def authenticate_user(login: str, password: str, db: Session): | ||||
| print("Trying to auth...") | print("Trying to auth...") | ||||
| user = None | user = None | ||||
| if ("@" in login): | |||||
| if "@" in login: | |||||
| user = get_user_by_email(email=login, db=db) | user = get_user_by_email(email=login, db=db) | ||||
| elif ("+" in login): | |||||
| elif "+" in login: | |||||
| user = get_user_by_phone(phone=login, db=db) | user = get_user_by_phone(phone=login, db=db) | ||||
| else: | else: | ||||
| return False | return False | ||||
| @@ -31,30 +35,61 @@ def authenticate_user(login: str, password: str, db: Session): | |||||
| return user | return user | ||||
| def get_current_user(token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[Session, Depends(get_db)]): | |||||
| def get_current_user( | |||||
| token: Annotated[str, Depends(oauth2_scheme)], | |||||
| db: Annotated[Session, Depends(get_db)], | |||||
| ): | |||||
| print("Getting current user...") | print("Getting current user...") | ||||
| try: | try: | ||||
| payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) | |||||
| payload = jwt.decode( | |||||
| token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] | |||||
| ) | |||||
| token_data = TokenPayload(**payload) | |||||
| if datetime.fromtimestamp(token_data.exp) < datetime.now(): | |||||
| raise HTTPException( | |||||
| status_code=status.HTTP_401_UNAUTHORIZED, | |||||
| detail="Session expired. Please login again.", | |||||
| ) | |||||
| username: str = payload.get("sub") | username: str = payload.get("sub") | ||||
| if username is None: | if username is None: | ||||
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials") | |||||
| raise HTTPException( | |||||
| status_code=status.HTTP_401_UNAUTHORIZED, | |||||
| detail="Could not validate credentials", | |||||
| ) | |||||
| except JWTError: | except JWTError: | ||||
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials") | |||||
| if ("@" in username): | |||||
| raise HTTPException( | |||||
| status_code=status.HTTP_401_UNAUTHORIZED, | |||||
| detail="Could not validate credentials", | |||||
| ) | |||||
| if "@" in username: | |||||
| user = get_user_by_email(email=username, db=db) | user = get_user_by_email(email=username, db=db) | ||||
| elif ("+" in username): | |||||
| elif "+" in username: | |||||
| user = get_user_by_phone(phone=username, db=db) | user = get_user_by_phone(phone=username, db=db) | ||||
| else: | else: | ||||
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials") | |||||
| raise HTTPException( | |||||
| status_code=status.HTTP_401_UNAUTHORIZED, | |||||
| detail="Could not validate credentials", | |||||
| ) | |||||
| return user | return user | ||||
| @router.post("/token", response_model=Token) | @router.post("/token", response_model=Token) | ||||
| def access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): | |||||
| def access_token( | |||||
| form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) | |||||
| ): | |||||
| print("Getting token...") | print("Getting token...") | ||||
| user = authenticate_user(form_data.username, form_data.password, db) | user = authenticate_user(form_data.username, form_data.password, db) | ||||
| print(user) | print(user) | ||||
| if not user: | if not user: | ||||
| raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password") | |||||
| raise HTTPException( | |||||
| status_code=status.HTTP_401_UNAUTHORIZED, | |||||
| detail="Invalid username or password", | |||||
| ) | |||||
| access_token = create_access_token(data={"sub": user.Email}) | access_token = create_access_token(data={"sub": user.Email}) | ||||
| return {"access_token": access_token, "token_type": "bearer"} | |||||
| print("TOKENS ARE: ") | |||||
| print(access_token) | |||||
| return { | |||||
| "access_token": access_token, | |||||
| "token_type": "bearer", | |||||
| } | |||||
| @@ -14,9 +14,15 @@ router = APIRouter() | |||||
| @router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED) | @router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED) | ||||
| def create_user(user: UserCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): | |||||
| if current_user.Role != "Admin": | |||||
| def create_user( | |||||
| user: UserCreate, | |||||
| db: Session = Depends(get_db), | |||||
| current_user: User = Depends(get_current_user), | |||||
| ): | |||||
| if (current_user.Role != "Admin"): | |||||
| raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | ||||
| # if current_user.Role != "Admin": | |||||
| # raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | |||||
| user = create_new_user(user=user, db=db) | user = create_new_user(user=user, db=db) | ||||
| return user | return user | ||||
| @@ -31,7 +37,10 @@ def get_all_users(db: Session = Depends(get_db), role: str = None): | |||||
| @router.get("/me", response_model=ShowUser, status_code=status.HTTP_200_OK) | @router.get("/me", response_model=ShowUser, status_code=status.HTTP_200_OK) | ||||
| def get_user_me(current_user: Annotated[User, Depends(get_current_user)], db: Annotated[Session, Depends(get_db)]): | |||||
| def get_user_me( | |||||
| current_user: Annotated[User, Depends(get_current_user)], | |||||
| db: Annotated[Session, Depends(get_db)], | |||||
| ): | |||||
| print("Getting current user...") | print("Getting current user...") | ||||
| return current_user | return current_user | ||||
| @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session | |||||
| from fastapi import Depends | from fastapi import Depends | ||||
| from typing import List | from typing import List | ||||
| from db.session import get_db | from db.session import get_db | ||||
| from schemas.vehicle import OutputVehicle, CreateVehicle, UpdateVehicle | |||||
| from schemas.vehicle import OutputVehicle, CreateVehicle, UpdateVehicle, VehicleLocation | |||||
| from db.repository.vehicle import ( | from db.repository.vehicle import ( | ||||
| create_new_vehicle, | create_new_vehicle, | ||||
| assign_vehicle_driver, | assign_vehicle_driver, | ||||
| @@ -11,6 +11,7 @@ from db.repository.vehicle import ( | |||||
| get_vehicle_by_id, | get_vehicle_by_id, | ||||
| replace_vehicle_data, | replace_vehicle_data, | ||||
| delete_vehicle_data, | delete_vehicle_data, | ||||
| update_vehicle_geoloc, | |||||
| ) | ) | ||||
| from db.models.user import User | from db.models.user import User | ||||
| from apis.v1.route_auth import get_current_user | from apis.v1.route_auth import get_current_user | ||||
| @@ -19,9 +20,16 @@ router = APIRouter() | |||||
| @router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED) | @router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED) | ||||
| async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): | |||||
| async def create_vehicle( | |||||
| vehicle: CreateVehicle, | |||||
| db: Session = Depends(get_db), | |||||
| current_user: User = Depends(get_current_user), | |||||
| ): | |||||
| print(current_user.Role) | |||||
| if current_user.Role != "Admin": | if current_user.Role != "Admin": | ||||
| raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not authorized to perform this action" | |||||
| ) | |||||
| vehicle = create_new_vehicle(vehicle=vehicle, db=db) | vehicle = create_new_vehicle(vehicle=vehicle, db=db) | ||||
| return vehicle | return vehicle | ||||
| @@ -52,9 +60,16 @@ async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db), | |||||
| response_model=OutputVehicle, | response_model=OutputVehicle, | ||||
| status_code=status.HTTP_200_OK, | status_code=status.HTTP_200_OK, | ||||
| ) | ) | ||||
| async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): | |||||
| async def assign_driver( | |||||
| vehicle_id: int, | |||||
| driver_id: int, | |||||
| db: Session = Depends(get_db), | |||||
| current_user: User = Depends(get_current_user), | |||||
| ): | |||||
| if current_user.Role != "Admin": | if current_user.Role != "Admin": | ||||
| raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not authorized to perform this action" | |||||
| ) | |||||
| vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | ||||
| if vehicle == "nodriver": | if vehicle == "nodriver": | ||||
| raise HTTPException( | raise HTTPException( | ||||
| @@ -67,7 +82,7 @@ async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(g | |||||
| if vehicle == "alreadyassigned": | if vehicle == "alreadyassigned": | ||||
| raise HTTPException( | raise HTTPException( | ||||
| status_code=400, | status_code=400, | ||||
| detail=f"Driver with id {driver_id} is already assigned to vehicle with id {vehicle_id}", | |||||
| detail=f"A driver is already assigned to vehicle with id {vehicle_id}", | |||||
| ) | ) | ||||
| return vehicle | return vehicle | ||||
| @@ -92,23 +107,60 @@ async def get_vehicle(vehicle_id: int, db: Session = Depends(get_db)): | |||||
| "/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | "/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | ||||
| ) | ) | ||||
| def update_vehicle( | def update_vehicle( | ||||
| vehicle_id: int, vehicle: UpdateVehicle, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) | |||||
| vehicle_id: int, | |||||
| vehicle: UpdateVehicle, | |||||
| db: Session = Depends(get_db), | |||||
| current_user: User = Depends(get_current_user), | |||||
| ): | ): | ||||
| if current_user.Role != "Admin": | if current_user.Role != "Admin": | ||||
| raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not authorized to perform this action" | |||||
| ) | |||||
| vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db) | vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db) | ||||
| if vehicleRes == "vehicleNotFound": | if vehicleRes == "vehicleNotFound": | ||||
| raise HTTPException(status_code=404, detail="Vehicle not found") | raise HTTPException(status_code=404, detail="Vehicle not found") | ||||
| elif vehicleRes == "badreq": | elif vehicleRes == "badreq": | ||||
| raise HTTPException(status_code=502, detail="Bad request") | raise HTTPException(status_code=502, detail="Bad request") | ||||
| elif vehicleRes == "driverNotFound": | |||||
| raise HTTPException(status_code=404, detail="Driver not found") | |||||
| return vehicleRes | return vehicleRes | ||||
| @router.delete("/{vehicle_id}", status_code=status.HTTP_200_OK) | @router.delete("/{vehicle_id}", status_code=status.HTTP_200_OK) | ||||
| def delete_vehicle(vehicle_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): | |||||
| def delete_vehicle( | |||||
| vehicle_id: int, | |||||
| db: Session = Depends(get_db), | |||||
| current_user: User = Depends(get_current_user), | |||||
| ): | |||||
| if current_user.Role != "Admin": | if current_user.Role != "Admin": | ||||
| raise HTTPException(status_code=403, detail="You are not authorized to perform this action") | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not authorized to perform this action" | |||||
| ) | |||||
| result = delete_vehicle_data(id=vehicle_id, db=db) | result = delete_vehicle_data(id=vehicle_id, db=db) | ||||
| if result == "vehicleNotFound": | if result == "vehicleNotFound": | ||||
| raise HTTPException(status_code=404, detail="Vehicle not found") | raise HTTPException(status_code=404, detail="Vehicle not found") | ||||
| return {"msg": "Vehicle deleted successfully"} | return {"msg": "Vehicle deleted successfully"} | ||||
| @router.post("/{vehicle_id}/location", status_code=status.HTTP_200_OK) | |||||
| def update_vehicle_location( | |||||
| vehicle_id: int, | |||||
| location: VehicleLocation, | |||||
| current_user: User = Depends(get_current_user), | |||||
| db: Session = Depends(get_db), | |||||
| ): | |||||
| print(current_user) | |||||
| print(current_user.Name) | |||||
| if current_user.Role != "Driver": | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not authorized to perform this action" | |||||
| ) | |||||
| if current_user.AssignedVehicle != vehicle_id: | |||||
| raise HTTPException( | |||||
| status_code=403, detail="You are not the correct car driver" | |||||
| ) | |||||
| print("FUNNY") | |||||
| vehicle = update_vehicle_geoloc(vehicle_id=vehicle_id, location=location, db=db) | |||||
| if vehicle == "vehiclenotfound": | |||||
| raise HTTPException(status_code=404, detail="Vehicle not found") | |||||
| return vehicle | |||||
| @@ -12,5 +12,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): | |||||
| else: | else: | ||||
| expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE) | expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE) | ||||
| to_encode.update({"exp": expire}) | to_encode.update({"exp": expire}) | ||||
| encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) | |||||
| encoded_jwt = jwt.encode( | |||||
| to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM | |||||
| ) | |||||
| return encoded_jwt | return encoded_jwt | ||||
| @@ -7,9 +7,31 @@ class Settings: | |||||
| POSTGRES_PORT: str = "5432" | POSTGRES_PORT: str = "5432" | ||||
| POSTGRES_DB: str = "VMSData" | POSTGRES_DB: str = "VMSData" | ||||
| DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}:{POSTGRES_PORT}/{POSTGRES_DB}" | DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}:{POSTGRES_PORT}/{POSTGRES_DB}" | ||||
| ACCESS_TOKEN_EXPIRE: int = 30 | |||||
| ACCESS_TOKEN_EXPIRE: int = 60 * 24 * 7 # 7 days | |||||
| SECRET_KEY: str = "tH357aC6oA7ofCaN3yTffYkRh" | SECRET_KEY: str = "tH357aC6oA7ofCaN3yTffYkRh" | ||||
| ALGORITHM: str = "HS256" | ALGORITHM: str = "HS256" | ||||
| settings = Settings() | settings = Settings() | ||||
| def createAdminAcc(): | |||||
| from db.session import SessionLocal | |||||
| from db.repository.user import create_new_user | |||||
| from schemas.user import UserCreate | |||||
| from db.models.user import User | |||||
| db = SessionLocal() | |||||
| user = UserCreate( | |||||
| Email="madi.turgunov@nu.edu.kz", | |||||
| Password="1234567", | |||||
| Name="Madi", | |||||
| LastName="Turgunov", | |||||
| ContactNumber="+77071234567", | |||||
| Role="Admin", | |||||
| BirthDate="2000-01-01T00:00:00+06:00", | |||||
| ) | |||||
| if db.query(User).filter(User.Email == user.Email).first(): | |||||
| return False | |||||
| create_new_user(user=user, db=db) | |||||
| return True | |||||
| @@ -1,4 +1,4 @@ | |||||
| # Base class for all sqlalchemy models | # Base class for all sqlalchemy models | ||||
| from sqlalchemy.ext.declarative import declarative_base | |||||
| Base = declarative_base() | |||||
| from db.base_class import Base | |||||
| from db.models.user import User | |||||
| from db.models.vehicle import Vehicle | |||||
| @@ -0,0 +1,14 @@ | |||||
| from typing import Any | |||||
| from sqlalchemy.ext.declarative import as_declarative, declared_attr | |||||
| @as_declarative() | |||||
| class Base: | |||||
| id: Any | |||||
| __name__: str | |||||
| # Generate __tablename__ automatically | |||||
| @declared_attr | |||||
| def __tablename__(cls) -> str: | |||||
| return cls.__name__.lower() | |||||
| @@ -1,10 +1,10 @@ | |||||
| # PostgreSQL table model for users | # PostgreSQL table model for users | ||||
| from sqlalchemy import Column, Integer, String, DateTime | |||||
| from sqlalchemy import Column, Integer, String, DateTime, ForeignKey | |||||
| from sqlalchemy.orm import relationship | |||||
| from db.base import Base | from db.base import Base | ||||
| class User(Base): | class User(Base): | ||||
| __tablename__ = "users" | |||||
| Id = Column(Integer, primary_key=True, index=True) | Id = Column(Integer, primary_key=True, index=True) | ||||
| Name = Column(String, nullable=False) | Name = Column(String, nullable=False) | ||||
| MiddleName = Column(String, nullable=True) | MiddleName = Column(String, nullable=True) | ||||
| @@ -14,4 +14,6 @@ class User(Base): | |||||
| Email = Column(String, nullable=False) | Email = Column(String, nullable=False) | ||||
| Role = Column(String, nullable=False) | Role = Column(String, nullable=False) | ||||
| DrivingLicenseNumber = Column(String, nullable=True) | DrivingLicenseNumber = Column(String, nullable=True) | ||||
| AssignedVehicle = Column(Integer, ForeignKey("vehicle.Id"), nullable=True) | |||||
| vehicle = relationship("Vehicle", back_populates="driver") | |||||
| HashedPassword = Column(String, nullable=False) | HashedPassword = Column(String, nullable=False) | ||||
| @@ -5,18 +5,19 @@ from sqlalchemy import ( | |||||
| String, | String, | ||||
| ARRAY, | ARRAY, | ||||
| ) | ) | ||||
| from sqlalchemy.orm import relationship | |||||
| from db.base import Base | from db.base import Base | ||||
| class Vehicle(Base): | class Vehicle(Base): | ||||
| __tablename__ = "vehicles" | |||||
| Id = Column(Integer, primary_key=True, index=True) | Id = Column(Integer, primary_key=True, index=True) | ||||
| Model = Column(String, nullable=False) | Model = Column(String, nullable=False) | ||||
| Year = Column(Integer, nullable=False) | Year = Column(Integer, nullable=False) | ||||
| LicensePlate = Column(String, nullable=False) | LicensePlate = Column(String, nullable=False) | ||||
| Type = Column(String, nullable=False) | |||||
| AssignedDriverIds = Column(ARRAY(Integer), nullable=True) | |||||
| CurrentLocation = Column(ARRAY(String), nullable=True) | CurrentLocation = Column(ARRAY(String), nullable=True) | ||||
| Fuel = Column(Integer, nullable=False) | Fuel = Column(Integer, nullable=False) | ||||
| Mileage = Column(Integer, nullable=False) | Mileage = Column(Integer, nullable=False) | ||||
| Status = Column(String, nullable=False) | |||||
| Capacity = Column(Integer, nullable=False) | |||||
| MaintenanceNotes = Column(ARRAY(String), nullable=True) | MaintenanceNotes = Column(ARRAY(String), nullable=True) | ||||
| driver = relationship("User", back_populates="vehicle") | |||||
| @@ -47,6 +47,13 @@ def verify_driver_exists(driver_id: int, db: Session): | |||||
| return True | return True | ||||
| def get_car_driver(vehicle_id: int, db: Session): | |||||
| driver = db.query(User).filter(User.AssignedVehicle == vehicle_id).first() | |||||
| if not driver: | |||||
| return False | |||||
| return driver | |||||
| def list_users(db: Session, role: str = "Any"): | def list_users(db: Session, role: str = "Any"): | ||||
| users = db.query(User).filter((User.Role == role) | (role == "Any")).all() | users = db.query(User).filter((User.Role == role) | (role == "Any")).all() | ||||
| return users | return users | ||||
| @@ -1,51 +1,72 @@ | |||||
| from sqlalchemy.orm import Session | from sqlalchemy.orm import Session | ||||
| from schemas.vehicle import CreateVehicle, UpdateVehicle | |||||
| from schemas.vehicle import CreateVehicle, UpdateVehicle, VehicleLocation | |||||
| from db.models.vehicle import Vehicle | from db.models.vehicle import Vehicle | ||||
| from db.repository.user import verify_driver_exists | |||||
| from db.base import User | |||||
| from db.repository.user import get_car_driver | |||||
| def create_new_vehicle(vehicle: CreateVehicle, db: Session): | def create_new_vehicle(vehicle: CreateVehicle, db: Session): | ||||
| vehicle_object = Vehicle( | vehicle_object = Vehicle( | ||||
| **vehicle.model_dump(), | **vehicle.model_dump(), | ||||
| Fuel=0, | Fuel=0, | ||||
| AssignedDriverIds=[], | |||||
| Status="Inactive", | |||||
| CurrentLocation=[], | CurrentLocation=[], | ||||
| MaintenanceNotes=[] | MaintenanceNotes=[] | ||||
| ) | ) | ||||
| db.add(vehicle_object) | db.add(vehicle_object) | ||||
| db.commit() | db.commit() | ||||
| db.refresh(vehicle_object) | db.refresh(vehicle_object) | ||||
| return vehicle | |||||
| return vehicle_object | |||||
| def update_vehicle_geoloc(vehicle_id: int, location: VehicleLocation, db: Session): | |||||
| vehicle_db = db.query(Vehicle).filter(Vehicle.Id == vehicle_id) | |||||
| vehicle_object = vehicle_db.first() | |||||
| if not vehicle_object: | |||||
| return "vehiclenotfound" | |||||
| print("Location: " + str(location.CurrentLocation)) | |||||
| vehicle_object.CurrentLocation = location.CurrentLocation | |||||
| db.add(vehicle_object) | |||||
| db.commit() | |||||
| db.refresh(vehicle_object) | |||||
| return vehicle_object | |||||
| def assign_vehicle_driver(vehicle_id: int, driver_id: int, db: Session): | def assign_vehicle_driver(vehicle_id: int, driver_id: int, db: Session): | ||||
| vehicledb = db.query(Vehicle).filter(Vehicle.Id == vehicle_id) | |||||
| vehicle = vehicledb.first() | |||||
| driver = ( | |||||
| db.query(User).filter((User.Id == driver_id) & (User.Role == "Driver")).first() | |||||
| ) | |||||
| print(driver) | |||||
| vehicle = db.query(Vehicle).filter(Vehicle.Id == vehicle_id).first() | |||||
| if not vehicle: | if not vehicle: | ||||
| return "novehicle" | 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" | |||||
| if not driver: | |||||
| return "nodriver" | |||||
| driver.AssignedVehicle = vehicle_id | |||||
| db.add(driver) | |||||
| db.commit() | |||||
| return vehicle | |||||
| def list_vehicles(db: Session): | def list_vehicles(db: Session): | ||||
| vehicles = db.query(Vehicle).all() | vehicles = db.query(Vehicle).all() | ||||
| # assign AssignedDriver to all vehicles based on their drivers | |||||
| for vehicle in vehicles: | |||||
| driver = get_car_driver(vehicle.Id, db) | |||||
| if driver: | |||||
| vehicle.AssignedDriver = driver.Id | |||||
| else: | |||||
| vehicle.AssignedDriver = None | |||||
| return vehicles | return vehicles | ||||
| def get_vehicle_by_id(vehicle_id: int, db: Session): | def get_vehicle_by_id(vehicle_id: int, db: Session): | ||||
| vehicle = db.query(Vehicle).filter(Vehicle.Id == vehicle_id).first() | vehicle = db.query(Vehicle).filter(Vehicle.Id == vehicle_id).first() | ||||
| driver = get_car_driver(vehicle.Id, db) | |||||
| if driver: | |||||
| vehicle.AssignedDriver = driver.Id | |||||
| else: | |||||
| vehicle.AssignedDriver = None | |||||
| return vehicle | return vehicle | ||||
| @@ -54,18 +75,22 @@ def replace_vehicle_data(id: int, vehicle: UpdateVehicle, db: Session): | |||||
| vehicle_object = vehicle_db.first() | vehicle_object = vehicle_db.first() | ||||
| if not vehicle_object: | if not vehicle_object: | ||||
| return "vehiclenotfound" | return "vehiclenotfound" | ||||
| vehicle_object.AssignedDriverIds = vehicle.AssignedDriverIds | |||||
| vehicle_object.CurrentLocation = vehicle.CurrentLocation | vehicle_object.CurrentLocation = vehicle.CurrentLocation | ||||
| vehicle_object.Fuel = vehicle.Fuel | vehicle_object.Fuel = vehicle.Fuel | ||||
| vehicle_object.LicensePlate = vehicle.LicensePlate | vehicle_object.LicensePlate = vehicle.LicensePlate | ||||
| vehicle_object.MaintenanceNotes = vehicle.MaintenanceNotes | vehicle_object.MaintenanceNotes = vehicle.MaintenanceNotes | ||||
| vehicle_object.Mileage = vehicle.Mileage | vehicle_object.Mileage = vehicle.Mileage | ||||
| vehicle_object.Model = vehicle.Model | vehicle_object.Model = vehicle.Model | ||||
| vehicle_object.Type = vehicle.Type | |||||
| vehicle_object.Status = vehicle.Status | |||||
| res = assign_vehicle_driver(id, vehicle.AssignedDriver, db) | |||||
| if res == "nodriver": | |||||
| return "driverNotFound" | |||||
| vehicle_object.Capacity = vehicle.Capacity | |||||
| vehicle_object.Year = vehicle.Year | vehicle_object.Year = vehicle.Year | ||||
| print(vehicle_object) | print(vehicle_object) | ||||
| db.add(vehicle_object) | db.add(vehicle_object) | ||||
| db.commit() | db.commit() | ||||
| vehicle_object.AssignedDriver = vehicle.AssignedDriver | |||||
| return vehicle_object | return vehicle_object | ||||
| @@ -1,5 +1,5 @@ | |||||
| from fastapi import FastAPI | from fastapi import FastAPI | ||||
| from core.config import settings | |||||
| from core.config import settings, createAdminAcc | |||||
| from db.session import engine | from db.session import engine | ||||
| from db.base import Base | from db.base import Base | ||||
| from apis.base import api_router | from apis.base import api_router | ||||
| @@ -10,8 +10,12 @@ def include_routes(app): # include all routes from our api/v1/ | |||||
| def startup(): # start the project, and create the tables | def startup(): # start the project, and create the tables | ||||
| app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION) | |||||
| app = FastAPI( | |||||
| title=settings.PROJECT_NAME, | |||||
| version=settings.PROJECT_VERSION, | |||||
| ) | |||||
| Base.metadata.create_all(bind=engine) | Base.metadata.create_all(bind=engine) | ||||
| createAdminAcc() | |||||
| include_routes(app) | include_routes(app) | ||||
| return app | return app | ||||
| @@ -4,3 +4,8 @@ from pydantic import BaseModel | |||||
| class Token(BaseModel): | class Token(BaseModel): | ||||
| access_token: str | access_token: str | ||||
| token_type: str | token_type: str | ||||
| class TokenPayload(BaseModel): | |||||
| sub: str = None | |||||
| exp: int = None | |||||
| @@ -8,7 +8,7 @@ class UserCreate(BaseModel): | |||||
| Email: EmailStr | Email: EmailStr | ||||
| Password: str = Field(..., min_length=7, max_length=20) | Password: str = Field(..., min_length=7, max_length=20) | ||||
| Name: str = Field(..., min_length=3, max_length=50) | Name: str = Field(..., min_length=3, max_length=50) | ||||
| MiddleName: str = Field(None, min_length=3, max_length=50) | |||||
| MiddleName: str = Field(None) | |||||
| LastName: str = Field(..., min_length=3, max_length=50) | LastName: str = Field(..., min_length=3, max_length=50) | ||||
| ContactNumber: str = Field(..., min_length=12, max_length=12) | ContactNumber: str = Field(..., min_length=12, max_length=12) | ||||
| BirthDate: datetime = Field(...) | BirthDate: datetime = Field(...) | ||||
| @@ -19,12 +19,14 @@ class UserCreate(BaseModel): | |||||
| class ShowUser(BaseModel): | class ShowUser(BaseModel): | ||||
| Id: int | Id: int | ||||
| Name: str | Name: str | ||||
| MiddleName: str | |||||
| MiddleName: str | None | |||||
| LastName: str | LastName: str | ||||
| ContactNumber: str | ContactNumber: str | ||||
| BirthDate: datetime | BirthDate: datetime | ||||
| Email: EmailStr | Email: EmailStr | ||||
| Role: str | Role: str | ||||
| AssignedVehicle: int | None | |||||
| class Config: | class Config: | ||||
| orm_mode = True | orm_mode = True | ||||
| validate_assignment = True | |||||
| @@ -3,12 +3,11 @@ from pydantic import BaseModel | |||||
| class CreateVehicle(BaseModel): | class CreateVehicle(BaseModel): | ||||
| Id: int | |||||
| Model: str | Model: str | ||||
| Year: int | Year: int | ||||
| LicensePlate: str | LicensePlate: str | ||||
| Type: str | |||||
| Mileage: int | Mileage: int | ||||
| Capacity: int | |||||
| class OutputVehicle(BaseModel): | class OutputVehicle(BaseModel): | ||||
| @@ -16,21 +15,27 @@ class OutputVehicle(BaseModel): | |||||
| Model: str | Model: str | ||||
| Year: int | Year: int | ||||
| LicensePlate: str | LicensePlate: str | ||||
| Type: str | |||||
| Mileage: int | Mileage: int | ||||
| CurrentLocation: Optional[list[str]] = None | CurrentLocation: Optional[list[str]] = None | ||||
| Fuel: Optional[int] = 0 | Fuel: Optional[int] = 0 | ||||
| MaintenanceNotes: Optional[list[str]] = None | MaintenanceNotes: Optional[list[str]] = None | ||||
| AssignedDriverIds: Optional[list[int]] = None | |||||
| AssignedDriver: Optional[int] = None | |||||
| Capacity: int | |||||
| Status: str | |||||
| class UpdateVehicle(BaseModel): | class UpdateVehicle(BaseModel): | ||||
| Model: str | Model: str | ||||
| Year: int | Year: int | ||||
| LicensePlate: str | LicensePlate: str | ||||
| Type: str | |||||
| Capacity: int | |||||
| Mileage: int | Mileage: int | ||||
| Status: str | |||||
| CurrentLocation: Optional[list[str]] = None | CurrentLocation: Optional[list[str]] = None | ||||
| Fuel: Optional[int] = 0 | Fuel: Optional[int] = 0 | ||||
| MaintenanceNotes: Optional[list[str]] = None | MaintenanceNotes: Optional[list[str]] = None | ||||
| AssignedDriverIds: Optional[list[int]] = None | |||||
| AssignedDriver: Optional[int] = None | |||||
| class VehicleLocation(BaseModel): | |||||
| CurrentLocation: list[str] | |||||