start
This commit is contained in:
0
backend/app/routers/__init__.py
Normal file
0
backend/app/routers/__init__.py
Normal file
23
backend/app/routers/auth.py
Normal file
23
backend/app/routers/auth.py
Normal 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))
|
||||
54
backend/app/routers/boxes.py
Normal file
54
backend/app/routers/boxes.py
Normal 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))
|
||||
61
backend/app/routers/objects.py
Normal file
61
backend/app/routers/objects.py
Normal 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)
|
||||
35
backend/app/routers/photos.py
Normal file
35
backend/app/routers/photos.py
Normal 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)
|
||||
30
backend/app/routers/qrcodes.py
Normal file
30
backend/app/routers/qrcodes.py
Normal 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)
|
||||
Reference in New Issue
Block a user