diff --git a/backend/__pycache__/main.cpython-314.pyc b/backend/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..4525cf1 Binary files /dev/null and b/backend/__pycache__/main.cpython-314.pyc differ diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 33b1e2b..fc80c41 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -5,8 +5,8 @@ from sqlalchemy import pool from alembic import context -from database import Base -from models import * +from app.db.database import Base +from app.models import * import os from dotenv import load_dotenv diff --git a/backend/app/__pycache__/schemas.cpython-314.pyc b/backend/app/__pycache__/schemas.cpython-314.pyc new file mode 100644 index 0000000..386e0dc Binary files /dev/null and b/backend/app/__pycache__/schemas.cpython-314.pyc differ diff --git a/backend/app/__pycache__/security.cpython-314.pyc b/backend/app/__pycache__/security.cpython-314.pyc new file mode 100644 index 0000000..dc7874b Binary files /dev/null and b/backend/app/__pycache__/security.cpython-314.pyc differ diff --git a/backend/app/db/__pycache__/database.cpython-314.pyc b/backend/app/db/__pycache__/database.cpython-314.pyc new file mode 100644 index 0000000..e333d3d Binary files /dev/null and b/backend/app/db/__pycache__/database.cpython-314.pyc differ diff --git a/backend/database.py b/backend/app/db/database.py similarity index 100% rename from backend/database.py rename to backend/app/db/database.py diff --git a/backend/init_db.py b/backend/app/init_db.py similarity index 88% rename from backend/init_db.py rename to backend/app/init_db.py index 802bcaa..942ad47 100644 --- a/backend/init_db.py +++ b/backend/app/init_db.py @@ -1,6 +1,6 @@ -from database import engine, Base, SessionLocal -from models import User, Project, Specialty, Contractor, Activity, NonConformity, Evidence, UserRole -from security import get_password_hash # Import hashing function +from app.db.database import engine, Base, SessionLocal +from app.models import User, Project, Specialty, Contractor, Activity, NonConformity, Evidence, UserRole +from app.security import get_password_hash # Import hashing function import datetime def init_db(): diff --git a/backend/migrate.py b/backend/app/migrate.py similarity index 100% rename from backend/migrate.py rename to backend/app/migrate.py diff --git a/backend/app/models/__pycache__/models.cpython-314.pyc b/backend/app/models/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..969e951 Binary files /dev/null and b/backend/app/models/__pycache__/models.cpython-314.pyc differ diff --git a/backend/models.py b/backend/app/models/models.py similarity index 99% rename from backend/models.py rename to backend/app/models/models.py index 4bc59bb..041e69e 100644 --- a/backend/models.py +++ b/backend/app/models/models.py @@ -1,6 +1,6 @@ from sqlalchemy import Table, Column, Integer, String, Boolean, ForeignKey, DateTime, Text, Enum, JSON from sqlalchemy.orm import relationship -from database import Base +from app.db.database import Base import datetime import enum diff --git a/backend/app/routers/__pycache__/activities.cpython-314.pyc b/backend/app/routers/__pycache__/activities.cpython-314.pyc new file mode 100644 index 0000000..c4dbeee Binary files /dev/null and b/backend/app/routers/__pycache__/activities.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/auth.cpython-314.pyc b/backend/app/routers/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000..1208b5e Binary files /dev/null and b/backend/app/routers/__pycache__/auth.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/contractors.cpython-314.pyc b/backend/app/routers/__pycache__/contractors.cpython-314.pyc new file mode 100644 index 0000000..5c48e92 Binary files /dev/null and b/backend/app/routers/__pycache__/contractors.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/guest.cpython-314.pyc b/backend/app/routers/__pycache__/guest.cpython-314.pyc new file mode 100644 index 0000000..42de0d0 Binary files /dev/null and b/backend/app/routers/__pycache__/guest.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/non_conformities.cpython-314.pyc b/backend/app/routers/__pycache__/non_conformities.cpython-314.pyc new file mode 100644 index 0000000..d02d7a6 Binary files /dev/null and b/backend/app/routers/__pycache__/non_conformities.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/projects.cpython-314.pyc b/backend/app/routers/__pycache__/projects.cpython-314.pyc new file mode 100644 index 0000000..b5865ca Binary files /dev/null and b/backend/app/routers/__pycache__/projects.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/specialties.cpython-314.pyc b/backend/app/routers/__pycache__/specialties.cpython-314.pyc new file mode 100644 index 0000000..afbbbdb Binary files /dev/null and b/backend/app/routers/__pycache__/specialties.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/transcription.cpython-314.pyc b/backend/app/routers/__pycache__/transcription.cpython-314.pyc new file mode 100644 index 0000000..7317dba Binary files /dev/null and b/backend/app/routers/__pycache__/transcription.cpython-314.pyc differ diff --git a/backend/app/routers/__pycache__/users.cpython-314.pyc b/backend/app/routers/__pycache__/users.cpython-314.pyc new file mode 100644 index 0000000..a471099 Binary files /dev/null and b/backend/app/routers/__pycache__/users.cpython-314.pyc differ diff --git a/backend/routers/activities.py b/backend/app/routers/activities.py similarity index 56% rename from backend/routers/activities.py rename to backend/app/routers/activities.py index 08e0216..ada3bc2 100644 --- a/backend/routers/activities.py +++ b/backend/app/routers/activities.py @@ -3,66 +3,61 @@ import shutil from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks from sqlalchemy.orm import Session from typing import List, Optional -from database import get_db -from models import Activity, Evidence, User -from security import get_current_active_user -import schemas import uuid -from services import activities +from app.db.database import get_db +from app.models.models import Activity, Evidence, User +from app.security import get_current_active_user +from app.services.activities import ActivityService +from app import schemas +from app.schemas import ActivityCreate, ActivityUpdate router = APIRouter( prefix="/activities", tags=["Activities"] ) +def get_activity_service( + db: Session = Depends(get_db), + current_user: User = Depends(get_current_active_user) +) -> ActivityService: + return ActivityService(db, current_user) + UPLOAD_DIR = "uploads" if not os.path.exists(UPLOAD_DIR): os.makedirs(UPLOAD_DIR) @router.post("/", response_model=schemas.Activity) def create_activity( - activity: schemas.ActivityCreate, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_active_user) + activity: ActivityCreate, + service: ActivityService = Depends(get_activity_service) ): - return activities.create_activity(db, activity, current_user) + return service.create_activity(activity) @router.get("/", response_model=List[schemas.Activity]) def read_activities( project_id: Optional[int] = None, specialty_id: Optional[int] = None, skip: int = 0, - limit: int = 100, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_active_user) + limit: int = 100, + service: ActivityService = Depends(get_activity_service) ): - db_activities = activities.get_activities(db, current_user, project_id, specialty_id, skip, limit) + db_activities = service.get_activities(project_id, specialty_id, skip, limit) return db_activities @router.get("/{activity_id}", response_model=schemas.Activity) def read_activity( - activity_id: int, - db: Session = Depends(get_db) + activity_id: int, + service: ActivityService = Depends(get_activity_service) ): - return activities.get_activity(db, activity_id) + return service.get_activity(activity_id) @router.put("/{activity_id}", response_model=schemas.Activity) def update_activity( activity_id: int, activity: schemas.ActivityUpdate, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_active_user) + service: ActivityService = Depends(get_activity_service) ): - db_activity = db.query(Activity).filter(Activity.id == activity_id).first() - if not db_activity: - raise HTTPException(status_code=404, detail="Activity not found") - - update_data = activity.dict(exclude_unset=True) - for key, value in update_data.items(): - setattr(db_activity, key, value) - - db.commit() - db.refresh(db_activity) + db_activity = service.update_activity(activity_id, activity) return db_activity @router.post("/{activity_id}/upload", response_model=schemas.Evidence) @@ -72,54 +67,17 @@ async def upload_evidence( file: UploadFile = File(...), description: Optional[str] = None, captured_at: Optional[str] = None, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_active_user) + service: ActivityService = Depends(get_activity_service) ): - # Verify activity exists - db_activity = db.query(Activity).filter(Activity.id == activity_id).first() - if not db_activity: - raise HTTPException(status_code=404, detail="Activity not found") - - # Generate unique filename - file_ext = os.path.splitext(file.filename)[1] - unique_filename = f"{uuid.uuid4()}{file_ext}" - file_path = os.path.join(UPLOAD_DIR, unique_filename) - - # Save file - with open(file_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - import datetime - db_captured_at = None - if captured_at: - try: - db_captured_at = datetime.datetime.fromisoformat(captured_at.replace('Z', '+00:00')) - except: - db_captured_at = datetime.datetime.utcnow() - - # Determine transcription status - initial_status = "none" - if file.content_type and "audio" in file.content_type: - initial_status = "pending" - - # Save to database - db_evidence = Evidence( - activity_id=activity_id, - file_path=file_path, - media_type=file.content_type, - description=description, - captured_at=db_captured_at, - transcription_status=initial_status - ) - db.add(db_evidence) - db.commit() - db.refresh(db_evidence) - - # If it's audio, queue transcription - if initial_status == "pending": - from services.transcription_worker import process_transcription - background_tasks.add_task(process_transcription, db_evidence.id) + db_evidence = service.upload_evidence( + activity_id, + background_tasks, + file, + description, + captured_at + ) + return db_evidence @router.post("/evidence/{evidence_id}/retry-transcription", response_model=schemas.Evidence) @@ -143,11 +101,10 @@ async def retry_transcription( db.refresh(db_evidence) # Queue transcription task - from services.transcription_worker import process_transcription + from app.services.transcription_worker import process_transcription background_tasks.add_task(process_transcription, db_evidence.id) return db_evidence - return db_evidence @router.put("/evidence/{evidence_id}", response_model=schemas.Evidence) def update_evidence( diff --git a/backend/routers/auth.py b/backend/app/routers/auth.py similarity index 81% rename from backend/routers/auth.py rename to backend/app/routers/auth.py index 1db1374..20d5052 100644 --- a/backend/routers/auth.py +++ b/backend/app/routers/auth.py @@ -1,15 +1,15 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session -from database import get_db -from models import User -from security import verify_password, create_access_token -import schemas from datetime import timedelta +from app.db.database import get_db +from app.models.models import User +from app.security import verify_password, create_access_token +import app.schemas router = APIRouter(tags=["Authentication"]) -@router.post("/token", response_model=schemas.Token) +@router.post("/token", response_model=app.schemas.Token) def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): user = db.query(User).filter(User.email == form_data.username).first() if not user or not verify_password(form_data.password, user.hashed_password): diff --git a/backend/routers/contractors.py b/backend/app/routers/contractors.py similarity index 77% rename from backend/routers/contractors.py rename to backend/app/routers/contractors.py index af101dc..93a9ea4 100644 --- a/backend/routers/contractors.py +++ b/backend/app/routers/contractors.py @@ -1,10 +1,10 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List, Optional -from database import get_db -from models import Contractor -from security import get_current_active_user -import schemas +from app.db.database import get_db +from app.models.models import Contractor +from app.security import get_current_active_user +import app.schemas router = APIRouter( prefix="/contractors", @@ -12,15 +12,15 @@ router = APIRouter( dependencies=[Depends(get_current_active_user)] ) -@router.post("/", response_model=schemas.Contractor) -def create_contractor(contractor: schemas.ContractorCreate, db: Session = Depends(get_db)): +@router.post("/", response_model=app.schemas.Contractor) +def create_contractor(contractor: app.schemas.ContractorCreate, db: Session = Depends(get_db)): db_contractor = Contractor(**contractor.dict()) db.add(db_contractor) db.commit() db.refresh(db_contractor) return db_contractor -@router.get("/", response_model=List[schemas.Contractor]) +@router.get("/", response_model=List[app.schemas.Contractor]) def read_contractors( parent_id: Optional[int] = None, only_parents: bool = False, @@ -37,18 +37,18 @@ def read_contractors( query = query.filter(Contractor.is_active == is_active) return query.all() -@router.get("/{contractor_id}", response_model=schemas.Contractor) +@router.get("/{contractor_id}", response_model=app.schemas.Contractor) def read_contractor(contractor_id: int, db: Session = Depends(get_db)): db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first() if not db_contractor: raise HTTPException(status_code=404, detail="Contractor not found") return db_contractor -@router.put("/{contractor_id}", response_model=schemas.Contractor) -@router.patch("/{contractor_id}", response_model=schemas.Contractor) +@router.put("/{contractor_id}", response_model=app.schemas.Contractor) +@router.patch("/{contractor_id}", response_model=app.schemas.Contractor) def update_contractor( contractor_id: int, - contractor: schemas.ContractorUpdate, + contractor: app.schemas.ContractorUpdate, db: Session = Depends(get_db) ): db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first() diff --git a/backend/routers/guest.py b/backend/app/routers/guest.py similarity index 88% rename from backend/routers/guest.py rename to backend/app/routers/guest.py index 1f0c042..676fb10 100644 --- a/backend/routers/guest.py +++ b/backend/app/routers/guest.py @@ -6,9 +6,9 @@ import datetime from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from sqlalchemy.orm import Session from typing import List, Optional -from database import get_db -from models import NonConformity, Evidence -import schemas +from app.db.database import get_db +from app.models.models import NonConformity, Evidence +import app.schemas router = APIRouter( prefix="/guest", @@ -19,7 +19,7 @@ UPLOAD_DIR = "uploads" if not os.path.exists(UPLOAD_DIR): os.makedirs(UPLOAD_DIR) -@router.get("/nc/{access_hash}", response_model=schemas.NonConformity) +@router.get("/nc/{access_hash}", response_model=app.schemas.NonConformity) def read_guest_nc( access_hash: str, db: Session = Depends(get_db) @@ -29,10 +29,10 @@ def read_guest_nc( raise HTTPException(status_code=404, detail="Non-Conformity not found or invalid link") return db_nc -@router.patch("/nc/{access_hash}", response_model=schemas.NonConformity) +@router.patch("/nc/{access_hash}", response_model=app.schemas.NonConformity) def update_guest_nc( access_hash: str, - nc_update: schemas.NonConformityUpdate, + nc_update: app.schemas.NonConformityUpdate, db: Session = Depends(get_db) ): db_nc = db.query(NonConformity).filter(NonConformity.access_hash == access_hash).first() @@ -62,7 +62,7 @@ def update_guest_nc( db.refresh(db_nc) return db_nc -@router.post("/nc/{access_hash}/upload", response_model=schemas.Evidence) +@router.post("/nc/{access_hash}/upload", response_model=app.schemas.Evidence) async def upload_guest_evidence( access_hash: str, file: UploadFile = File(...), diff --git a/backend/routers/non_conformities.py b/backend/app/routers/non_conformities.py similarity index 88% rename from backend/routers/non_conformities.py rename to backend/app/routers/non_conformities.py index 6607488..4c62e68 100644 --- a/backend/routers/non_conformities.py +++ b/backend/app/routers/non_conformities.py @@ -5,10 +5,10 @@ import datetime from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from sqlalchemy.orm import Session from typing import List, Optional -from database import get_db -from models import NonConformity, User, Activity, Evidence, Contractor -from security import get_current_active_user -import schemas +from app.db.database import get_db +from app.models.models import NonConformity, User, Activity, Evidence, Contractor +from app.security import get_current_active_user +import app.schemas router = APIRouter( prefix="/non-conformities", @@ -19,9 +19,9 @@ UPLOAD_DIR = "uploads" if not os.path.exists(UPLOAD_DIR): os.makedirs(UPLOAD_DIR) -@router.post("/", response_model=schemas.NonConformity) +@router.post("/", response_model=app.schemas.NonConformity) def create_nc( - nc: schemas.NonConformityCreate, + nc: app.schemas.NonConformityCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): @@ -41,7 +41,7 @@ def create_nc( db.refresh(db_nc) return db_nc -@router.get("/", response_model=List[schemas.NonConformity]) +@router.get("/", response_model=List[app.schemas.NonConformity]) def read_ncs( activity_id: Optional[int] = None, status: Optional[str] = None, @@ -56,7 +56,7 @@ def read_ncs( return query.all() -@router.get("/{nc_id}", response_model=schemas.NonConformity) +@router.get("/{nc_id}", response_model=app.schemas.NonConformity) def read_nc( nc_id: int, db: Session = Depends(get_db), @@ -67,11 +67,11 @@ def read_nc( raise HTTPException(status_code=404, detail="Non-Conformity not found") return db_nc -@router.put("/{nc_id}", response_model=schemas.NonConformity) -@router.patch("/{nc_id}", response_model=schemas.NonConformity) +@router.put("/{nc_id}", response_model=app.schemas.NonConformity) +@router.patch("/{nc_id}", response_model=app.schemas.NonConformity) def update_nc( nc_id: int, - nc: schemas.NonConformityUpdate, + nc: app.schemas.NonConformityUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): @@ -95,7 +95,7 @@ def update_nc( db.refresh(db_nc) return db_nc -@router.post("/{nc_id}/upload", response_model=schemas.Evidence) +@router.post("/{nc_id}/upload", response_model=app.schemas.Evidence) async def upload_nc_evidence( nc_id: int, file: UploadFile = File(...), @@ -175,7 +175,7 @@ def notify_responsible( db.refresh(db_nc) # Send email - from services.email_service import EmailService + from app.services.email_service import EmailService EmailService.send_nc_notification(db_nc.responsible_email, db_nc.access_hash, db_nc.description) # Update status to in-checking diff --git a/backend/routers/projects.py b/backend/app/routers/projects.py similarity index 84% rename from backend/routers/projects.py rename to backend/app/routers/projects.py index bfa7745..e3df68c 100644 --- a/backend/routers/projects.py +++ b/backend/app/routers/projects.py @@ -1,10 +1,10 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List -from database import get_db -from models import Project, Specialty, Contractor -from security import get_current_active_user -import schemas +from app.db.database import get_db +from app.models.models import Project, Specialty, Contractor +from app.security import get_current_active_user +import app.schemas router = APIRouter( prefix="/projects", @@ -12,8 +12,8 @@ router = APIRouter( dependencies=[Depends(get_current_active_user)] ) -@router.post("/", response_model=schemas.Project) -def create_project(project: schemas.ProjectCreate, db: Session = Depends(get_db)): +@router.post("/", response_model=app.schemas.Project) +def create_project(project: app.schemas.ProjectCreate, db: Session = Depends(get_db)): db_project = db.query(Project).filter(Project.code == project.code).first() if db_project: raise HTTPException(status_code=400, detail="Project code already exists") @@ -41,20 +41,20 @@ def create_project(project: schemas.ProjectCreate, db: Session = Depends(get_db) db.refresh(db_project) return db_project -@router.get("/", response_model=List[schemas.Project]) +@router.get("/", response_model=List[app.schemas.Project]) def read_projects(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): projects = db.query(Project).offset(skip).limit(limit).all() return projects -@router.get("/{project_id}", response_model=schemas.Project) +@router.get("/{project_id}", response_model=app.schemas.Project) def read_project(project_id: int, db: Session = Depends(get_db)): db_project = db.query(Project).filter(Project.id == project_id).first() if db_project is None: raise HTTPException(status_code=404, detail="Project not found") return db_project -@router.put("/{project_id}", response_model=schemas.Project) -def update_project(project_id: int, project: schemas.ProjectCreate, db: Session = Depends(get_db)): +@router.put("/{project_id}", response_model=app.schemas.Project) +def update_project(project_id: int, project: app.schemas.ProjectCreate, db: Session = Depends(get_db)): db_project = db.query(Project).filter(Project.id == project_id).first() if db_project is None: raise HTTPException(status_code=404, detail="Project not found") diff --git a/backend/routers/specialties.py b/backend/app/routers/specialties.py similarity index 61% rename from backend/routers/specialties.py rename to backend/app/routers/specialties.py index 311342d..1301a2d 100644 --- a/backend/routers/specialties.py +++ b/backend/app/routers/specialties.py @@ -1,10 +1,10 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from typing import List -from database import get_db -from models import Specialty -from security import get_current_active_user -import schemas +from app.db.database import get_db +from app.models.models import Specialty +from app.security import get_current_active_user +import app.schemas router = APIRouter( prefix="/specialties", @@ -12,6 +12,6 @@ router = APIRouter( dependencies=[Depends(get_current_active_user)] ) -@router.get("/", response_model=List[schemas.Specialty]) +@router.get("/", response_model=List[app.schemas.Specialty]) def read_specialties(db: Session = Depends(get_db)): return db.query(Specialty).all() diff --git a/backend/routers/transcription.py b/backend/app/routers/transcription.py similarity index 97% rename from backend/routers/transcription.py rename to backend/app/routers/transcription.py index 28874e1..fe116cd 100644 --- a/backend/routers/transcription.py +++ b/backend/app/routers/transcription.py @@ -1,9 +1,9 @@ import os from fastapi import APIRouter, Depends, UploadFile, File, HTTPException -from security import get_current_active_user import google.generativeai as genai import tempfile from dotenv import load_dotenv +from app.security import get_current_active_user load_dotenv() diff --git a/backend/routers/users.py b/backend/app/routers/users.py similarity index 77% rename from backend/routers/users.py rename to backend/app/routers/users.py index e2e39df..7c4ac7a 100644 --- a/backend/routers/users.py +++ b/backend/app/routers/users.py @@ -1,11 +1,11 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List -from database import get_db -from models import User -from security import get_password_hash, get_current_active_user -from services import users -from schemas import UserCreate, User +from app.db.database import get_db +from app.models.models import User +from app.security import get_password_hash, get_current_active_user +from app.services import users +from app.schemas import UserCreate, User router = APIRouter( prefix="/users", diff --git a/backend/schemas.py b/backend/app/schemas.py similarity index 98% rename from backend/schemas.py rename to backend/app/schemas.py index 04b798b..09f5ead 100644 --- a/backend/schemas.py +++ b/backend/app/schemas.py @@ -1,7 +1,7 @@ from pydantic import BaseModel, EmailStr, field_validator from typing import Optional, List from datetime import datetime -from models import UserRole, ActivityType, NCLevel, NCType +from app.models.models import UserRole, ActivityType, NCLevel, NCType # Token class Token(BaseModel): diff --git a/backend/security.py b/backend/app/security.py similarity index 93% rename from backend/security.py rename to backend/app/security.py index 9b41482..7bc43ac 100644 --- a/backend/security.py +++ b/backend/app/security.py @@ -5,9 +5,9 @@ from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session -from database import get_db -from models import User -import schemas +from app.db.database import get_db +from app.models.models import User +import app.schemas # Secret key for JWT (should be in env vars in production) SECRET_KEY = "super_secret_key_for_fritos_fresh_supervision_system" @@ -44,7 +44,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = De email: str = payload.get("sub") if email is None: raise credentials_exception - token_data = schemas.TokenData(email=email) + token_data = app.schemas.TokenData(email=email) except JWTError: raise credentials_exception user = db.query(User).filter(User.email == token_data.email).first() diff --git a/backend/app/services/__pycache__/activities.cpython-314.pyc b/backend/app/services/__pycache__/activities.cpython-314.pyc new file mode 100644 index 0000000..2e0b3ed Binary files /dev/null and b/backend/app/services/__pycache__/activities.cpython-314.pyc differ diff --git a/backend/app/services/__pycache__/users.cpython-314.pyc b/backend/app/services/__pycache__/users.cpython-314.pyc new file mode 100644 index 0000000..6554578 Binary files /dev/null and b/backend/app/services/__pycache__/users.cpython-314.pyc differ diff --git a/backend/app/services/activities.py b/backend/app/services/activities.py new file mode 100644 index 0000000..ff8fc68 --- /dev/null +++ b/backend/app/services/activities.py @@ -0,0 +1,120 @@ +import os +import uuid +import shutil +import datetime +from sqlalchemy.orm import Session +from typing import Optional +from fastapi import HTTPException, File, BackgroundTasks, UploadFile +from app.models.models import Activity, User, Evidence +from app.schemas import ActivityCreate, ActivityUpdate +from app.db.database import get_db +from app.security import get_current_active_user + +UPLOAD_DIR = "uploads" + +class ActivityService: + def __init__(self, db: Session, current_user: User): + self._db = db + self._current_user = current_user + + def get_activities(self, project_id: Optional[int] = None, + specialty_id: Optional[int] = None, + skip: int = 0, + limit: int = 100): + + query = self._db.query(Activity) + if project_id: + query = query.filter(Activity.project_id == project_id) + if specialty_id: + query = query.filter(Activity.specialty_id == specialty_id) + + activities = query.offset(skip).limit(limit).all() + return activities + + def get_activity(self, activity_id: int): + db_activity = self._db.query(Activity).filter(Activity.id == activity_id).first() + if db_activity is None: + raise HTTPException(status_code=404, detail="Activity not found") + return db_activity + + def create_activity(self, activity: ActivityCreate): + db_activity = Activity( + **activity.dict(), + user_id=self._current_user.id + ) + self._db.add(db_activity) + self._db.commit() + self._db.refresh(db_activity) + return db_activity + + def update_activity(self, activity_id: int, activity: ActivityUpdate): + db_activity = self._db.query(Activity).filter(Activity.id == activity_id).first() + if not db_activity: + raise HTTPException(status_code=404, detail="Activity not found") + + update_data = activity.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_activity, key, value) + + self._db.commit() + self._db.refresh(db_activity) + return db_activity + + def upload_evidence( + self, + activity_id: int, + background_tasks: BackgroundTasks, + file: UploadFile = File(...), + description: Optional[str] = None, + captured_at: Optional[str] = None): + + # Verify activity exists + db_activity = self._db.query(Activity).filter(Activity.id == activity_id).first() + if not db_activity: + raise HTTPException(status_code=404, detail="Activity not found") + + # Generate unique filename + file_ext = os.path.splitext(file.filename)[1] + unique_filename = f"{uuid.uuid4()}{file_ext}" + file_path = os.path.join(UPLOAD_DIR, unique_filename) + + # Save file + if not os.path.exists(UPLOAD_DIR): + os.makedirs(UPLOAD_DIR) + + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + db_captured_at = None + if captured_at: + try: + db_captured_at = datetime.datetime.fromisoformat(captured_at.replace('Z', '+00:00')) + except: + db_captured_at = datetime.datetime.utcnow() + else: + db_captured_at = datetime.datetime.utcnow() + + # Determine transcription status + initial_status = "none" + if file.content_type and "audio" in file.content_type: + initial_status = "pending" + + # Save to database + db_evidence = Evidence( + activity_id=activity_id, + file_path=file_path, + media_type=file.content_type, + description=description, + captured_at=db_captured_at, + transcription_status=initial_status + ) + self._db.add(db_evidence) + self._db.commit() + self._db.refresh(db_evidence) + + # If it's audio, queue transcription + if initial_status == "pending": + from app.services.transcription_worker import process_transcription + background_tasks.add_task(process_transcription, db_evidence.id) + + return db_evidence \ No newline at end of file diff --git a/backend/services/email_service.py b/backend/app/services/email_service.py similarity index 100% rename from backend/services/email_service.py rename to backend/app/services/email_service.py diff --git a/backend/services/transcription_worker.py b/backend/app/services/transcription_worker.py similarity index 97% rename from backend/services/transcription_worker.py rename to backend/app/services/transcription_worker.py index 87f1f53..512530b 100644 --- a/backend/services/transcription_worker.py +++ b/backend/app/services/transcription_worker.py @@ -1,9 +1,9 @@ import os import time from sqlalchemy.orm import Session -from models import Evidence import google.generativeai as genai -from database import SessionLocal +from app.models import Evidence +from app.database import SessionLocal def process_transcription(evidence_id: int): """ diff --git a/backend/services/users.py b/backend/app/services/users.py similarity index 85% rename from backend/services/users.py rename to backend/app/services/users.py index 4f39fb4..747ff6c 100644 --- a/backend/services/users.py +++ b/backend/app/services/users.py @@ -1,8 +1,8 @@ from sqlalchemy.orm import Session -from models import User -from security import get_password_hash -from schemas import UserCreate from fastapi import HTTPException +from app.models.models import User +from app.security import get_password_hash +from app.schemas import UserCreate def get_users(db: Session, skip: int = 0, limit: int = 100): return db.query(User).offset(skip).limit(limit).all() diff --git a/backend/main.py b/backend/main.py index 8e68500..af81544 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,7 +11,7 @@ if sys.version_info < (3, 10): from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from routers import auth, users, projects, activities, specialties, contractors, transcription, non_conformities, guest +from app.routers import auth, users, projects, activities, specialties, contractors, transcription, non_conformities, guest import os from fastapi.staticfiles import StaticFiles diff --git a/backend/requirements.txt b/backend/requirements.txt index cb6d087..b189d20 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,24 +1,22 @@ -alembic==1.16.5 +alembic==1.17.2 annotated-doc==0.0.4 annotated-types==0.7.0 anyio==4.12.0 -bcrypt==3.2.2 +bcrypt==5.0.0 cachetools==6.2.4 certifi==2025.11.12 cffi==2.0.0 charset-normalizer==3.4.4 -click==8.1.8 +click==8.3.1 cryptography==46.0.3 -dnspython==2.7.0 +dnspython==2.8.0 ecdsa==0.19.1 email-validator==2.3.0 exceptiongroup==1.3.1 -fastapi==0.125.0 -fastapi-cli==0.0.16 -fastapi-cloud-cli==0.7.0 -fastar==0.8.0 +fastapi==0.128.0 +fastapi-cli==0.0.20 google-ai-generativelanguage==0.6.15 -google-api-core==2.28.1 +google-api-core==2.25.2 google-api-python-client==2.187.0 google-auth==2.45.0 google-auth-httplib2==0.3.0 @@ -32,18 +30,12 @@ httplib2==0.31.0 httptools==0.7.1 httpx==0.28.1 idna==3.11 -iniconfig==2.1.0 -Jinja2==3.1.6 Mako==1.3.10 -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 MarkupSafe==3.0.3 mdurl==0.1.2 -numpy==2.0.2 +numpy==2.2.6 opencv-python-headless==4.12.0.88 -packaging==25.0 -passlib==1.7.4 -pillow==11.3.0 -pluggy==1.6.0 proto-plus==1.27.0 protobuf==5.29.5 psycopg2-binary==2.9.11 @@ -53,30 +45,26 @@ pycparser==2.23 pydantic==2.12.5 pydantic_core==2.41.5 Pygments==2.19.2 -pyparsing==3.2.5 -pytest==8.4.2 +pyparsing==3.3.1 python-dotenv==1.2.1 python-jose==3.5.0 -python-multipart==0.0.20 +python-multipart==0.0.21 PyYAML==6.0.3 requests==2.32.5 rich==14.2.0 rich-toolkit==0.17.1 -rignore==0.7.6 rsa==4.9.1 -sentry-sdk==2.48.0 shellingham==1.5.4 six==1.17.0 SQLAlchemy==2.0.45 -starlette==0.49.3 -tomli==2.3.0 +starlette==0.50.0 tqdm==4.67.1 -typer==0.20.0 +typer==0.21.0 typing-inspection==0.4.2 typing_extensions==4.15.0 uritemplate==4.2.0 urllib3==2.6.2 -uvicorn==0.38.0 +uvicorn==0.40.0 uvloop==0.22.1 watchfiles==1.1.1 websockets==15.0.1 diff --git a/backend/services/activities.py b/backend/services/activities.py deleted file mode 100644 index 733a62a..0000000 --- a/backend/services/activities.py +++ /dev/null @@ -1,35 +0,0 @@ -from sqlalchemy.orm import Session -from typing import Optional -from models import Activity, User -from schemas import ActivityCreate -from fastapi import HTTPException - -def get_activities(db: Session, current_user: User, project_id: Optional[int] = None, - specialty_id: Optional[int] = None, - skip: int = 0, - limit: int = 100): - - query = db.query(Activity) - if project_id: - query = query.filter(Activity.project_id == project_id) - if specialty_id: - query = query.filter(Activity.specialty_id == specialty_id) - - activities = query.offset(skip).limit(limit).all() - return activities - -def get_activity(db: Session, activity_id: int): - db_activity = db.query(Activity).filter(Activity.id == activity_id).first() - if db_activity is None: - raise HTTPException(status_code=404, detail="Activity not found") - return db_activity - -def create_activity(db: Session, activity: ActivityCreate, current_user: User): - db_activity = Activity( - **activity.dict(), - user_id=current_user.id - ) - db.add(db_activity) - db.commit() - db.refresh(db_activity) - return db_activity \ No newline at end of file diff --git a/frontend/src/environments/environment.development.ts b/frontend/src/environments/environment.development.ts index 0ff0a91..e1ee2b8 100644 --- a/frontend/src/environments/environment.development.ts +++ b/frontend/src/environments/environment.development.ts @@ -1,3 +1,3 @@ export const environment = { - apiUrl: 'http://192.168.1.74:8000' + apiUrl: 'http://192.168.1.76:8000' }; diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 0ff0a91..e1ee2b8 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -1,3 +1,3 @@ export const environment = { - apiUrl: 'http://192.168.1.74:8000' + apiUrl: 'http://192.168.1.76:8000' };