This commit is contained in:
jze9
2026-07-21 16:11:09 +05:00
commit 78862296c4
94 changed files with 6090 additions and 0 deletions

View File

View File

@@ -0,0 +1,23 @@
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from app.core.dependencies import DbSession
from app.core.security import create_access_token
from app.schemas.auth import Token
from app.services.auth import authenticate_user
router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
@router.post("/token", response_model=Token)
async def login(db: DbSession, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
user = await authenticate_user(db, form_data.username, form_data.password)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return Token(access_token=create_access_token(user.id))

View File

@@ -0,0 +1,54 @@
import uuid
from fastapi import APIRouter, HTTPException, status
from app.core.dependencies import CurrentUser, DbSession
from app.schemas.box import BoxCreate, BoxRead, BoxSummary, BoxUpdate
from app.services import boxes as boxes_service
router = APIRouter(prefix="/api/v1/boxes", tags=["boxes"])
@router.get("", response_model=list[BoxSummary])
async def list_boxes(
db: DbSession, search: str | None = None, root_only: bool = False
) -> list[BoxSummary]:
items = await boxes_service.list_boxes(db, search=search, root_only=root_only)
return [BoxSummary.model_validate(b) for b in items]
@router.post("", response_model=BoxRead, status_code=status.HTTP_201_CREATED)
async def create_box(db: DbSession, _: CurrentUser, data: BoxCreate) -> BoxRead:
box = await boxes_service.create_box(db, data)
return await boxes_service.build_box_read(db, box)
@router.get("/{box_id}", response_model=BoxRead)
async def get_box(db: DbSession, box_id: uuid.UUID) -> BoxRead:
box = await boxes_service.get_box(db, box_id)
if box is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Box not found")
return await boxes_service.build_box_read(db, box)
@router.put("/{box_id}", response_model=BoxRead)
async def update_box(db: DbSession, _: CurrentUser, box_id: uuid.UUID, data: BoxUpdate) -> BoxRead:
box = await boxes_service.get_box(db, box_id)
if box is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Box not found")
try:
box = await boxes_service.update_box(db, box, data)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
return await boxes_service.build_box_read(db, box)
@router.delete("/{box_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_box(db: DbSession, _: CurrentUser, box_id: uuid.UUID) -> None:
box = await boxes_service.get_box(db, box_id)
if box is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Box not found")
try:
await boxes_service.delete_box(db, box)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))

View File

@@ -0,0 +1,61 @@
import uuid
from fastapi import APIRouter, HTTPException, status
from sqlalchemy.exc import IntegrityError
from app.core.dependencies import CurrentUser, DbSession, get_minio_client
from app.schemas.object import ObjectCreate, ObjectRead, ObjectUpdate
from app.services import objects as objects_service
router = APIRouter(prefix="/api/v1/objects", tags=["objects"])
@router.get("", response_model=list[ObjectRead])
async def list_objects(db: DbSession, search: str | None = None) -> list[ObjectRead]:
items = await objects_service.list_objects(db, search=search)
return [objects_service.build_object_read(o) for o in items]
@router.post("", response_model=ObjectRead, status_code=status.HTTP_201_CREATED)
async def create_object(db: DbSession, _: CurrentUser, data: ObjectCreate) -> ObjectRead:
try:
obj = await objects_service.create_object(db, data)
except IntegrityError:
await db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Inventory number already in use"
)
return objects_service.build_object_read(obj)
@router.get("/{object_id}", response_model=ObjectRead)
async def get_object(db: DbSession, object_id: uuid.UUID) -> ObjectRead:
obj = await objects_service.get_object(db, object_id)
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
return objects_service.build_object_read(obj)
@router.put("/{object_id}", response_model=ObjectRead)
async def update_object(
db: DbSession, _: CurrentUser, object_id: uuid.UUID, data: ObjectUpdate
) -> ObjectRead:
obj = await objects_service.get_object(db, object_id)
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
try:
obj = await objects_service.update_object(db, obj, data)
except IntegrityError:
await db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Inventory number already in use"
)
return objects_service.build_object_read(obj)
@router.delete("/{object_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_object(db: DbSession, _: CurrentUser, object_id: uuid.UUID) -> None:
obj = await objects_service.get_object(db, object_id)
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
await objects_service.delete_object(db, get_minio_client(), obj)

View File

@@ -0,0 +1,35 @@
import uuid
from fastapi import APIRouter, HTTPException, UploadFile, status
from app.core.dependencies import CurrentUser, DbSession, get_minio_client
from app.models.object_photo import ObjectPhoto
from app.schemas.photo import PhotoRead
from app.services import objects as objects_service
from app.services.photos import build_photo_read, delete_photo, upload_photo
router = APIRouter(prefix="/api/v1", tags=["photos"])
@router.post(
"/objects/{object_id}/photos", response_model=PhotoRead, status_code=status.HTTP_201_CREATED
)
async def upload_object_photo(
db: DbSession, _: CurrentUser, object_id: uuid.UUID, file: UploadFile
) -> PhotoRead:
obj = await objects_service.get_object(db, object_id)
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
try:
photo = await upload_photo(db, get_minio_client(), object_id, file)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
return build_photo_read(photo)
@router.delete("/photos/{photo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_object_photo(db: DbSession, _: CurrentUser, photo_id: uuid.UUID) -> None:
photo = await db.get(ObjectPhoto, photo_id)
if photo is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Photo not found")
await delete_photo(db, get_minio_client(), photo)

View File

@@ -0,0 +1,30 @@
import uuid
from fastapi import APIRouter, HTTPException, Response, status
from app.core.dependencies import DbSession
from app.services import boxes as boxes_service
from app.services import objects as objects_service
from app.services.qrcodes import box_public_url, generate_qr_png, object_public_url
router = APIRouter(prefix="/api/v1", tags=["qrcodes"])
_PNG_HEADERS = {"Cache-Control": "public, max-age=86400"}
@router.get("/objects/{object_id}/qrcode.png")
async def object_qrcode(db: DbSession, object_id: uuid.UUID) -> Response:
obj = await objects_service.get_object(db, object_id)
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
png = generate_qr_png(object_public_url(obj.id))
return Response(content=png, media_type="image/png", headers=_PNG_HEADERS)
@router.get("/boxes/{box_id}/qrcode.png")
async def box_qrcode(db: DbSession, box_id: uuid.UUID) -> Response:
box = await boxes_service.get_box(db, box_id)
if box is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Box not found")
png = generate_qr_png(box_public_url(box.id))
return Response(content=png, media_type="image/png", headers=_PNG_HEADERS)