Reorganizacion de proyecto

This commit is contained in:
Luis Sanchez 2025-12-27 16:51:09 -05:00
parent 0d78fb9ccb
commit 93485eb700
41 changed files with 243 additions and 213 deletions

Binary file not shown.

View File

@ -5,8 +5,8 @@ from sqlalchemy import pool
from alembic import context from alembic import context
from database import Base from app.db.database import Base
from models import * from app.models import *
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
from database import engine, Base, SessionLocal from app.db.database import engine, Base, SessionLocal
from models import User, Project, Specialty, Contractor, Activity, NonConformity, Evidence, UserRole from app.models import User, Project, Specialty, Contractor, Activity, NonConformity, Evidence, UserRole
from security import get_password_hash # Import hashing function from app.security import get_password_hash # Import hashing function
import datetime import datetime
def init_db(): def init_db():

Binary file not shown.

View File

@ -1,6 +1,6 @@
from sqlalchemy import Table, Column, Integer, String, Boolean, ForeignKey, DateTime, Text, Enum, JSON from sqlalchemy import Table, Column, Integer, String, Boolean, ForeignKey, DateTime, Text, Enum, JSON
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from database import Base from app.db.database import Base
import datetime import datetime
import enum import enum

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,29 +3,35 @@ import shutil
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, BackgroundTasks
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional 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 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( router = APIRouter(
prefix="/activities", prefix="/activities",
tags=["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" UPLOAD_DIR = "uploads"
if not os.path.exists(UPLOAD_DIR): if not os.path.exists(UPLOAD_DIR):
os.makedirs(UPLOAD_DIR) os.makedirs(UPLOAD_DIR)
@router.post("/", response_model=schemas.Activity) @router.post("/", response_model=schemas.Activity)
def create_activity( def create_activity(
activity: schemas.ActivityCreate, activity: ActivityCreate,
db: Session = Depends(get_db), service: ActivityService = Depends(get_activity_service)
current_user: User = Depends(get_current_active_user)
): ):
return activities.create_activity(db, activity, current_user) return service.create_activity(activity)
@router.get("/", response_model=List[schemas.Activity]) @router.get("/", response_model=List[schemas.Activity])
def read_activities( def read_activities(
@ -33,36 +39,25 @@ def read_activities(
specialty_id: Optional[int] = None, specialty_id: Optional[int] = None,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
db: Session = Depends(get_db), service: ActivityService = Depends(get_activity_service)
current_user: User = Depends(get_current_active_user)
): ):
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 return db_activities
@router.get("/{activity_id}", response_model=schemas.Activity) @router.get("/{activity_id}", response_model=schemas.Activity)
def read_activity( def read_activity(
activity_id: int, activity_id: int,
db: Session = Depends(get_db) 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) @router.put("/{activity_id}", response_model=schemas.Activity)
def update_activity( def update_activity(
activity_id: int, activity_id: int,
activity: schemas.ActivityUpdate, activity: schemas.ActivityUpdate,
db: Session = Depends(get_db), service: ActivityService = Depends(get_activity_service)
current_user: User = Depends(get_current_active_user)
): ):
db_activity = db.query(Activity).filter(Activity.id == activity_id).first() db_activity = service.update_activity(activity_id, activity)
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)
return db_activity return db_activity
@router.post("/{activity_id}/upload", response_model=schemas.Evidence) @router.post("/{activity_id}/upload", response_model=schemas.Evidence)
@ -72,53 +67,16 @@ async def upload_evidence(
file: UploadFile = File(...), file: UploadFile = File(...),
description: Optional[str] = None, description: Optional[str] = None,
captured_at: Optional[str] = None, captured_at: Optional[str] = None,
db: Session = Depends(get_db), service: ActivityService = Depends(get_activity_service)
current_user: User = Depends(get_current_active_user)
): ):
# 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 db_evidence = service.upload_evidence(
file_ext = os.path.splitext(file.filename)[1] activity_id,
unique_filename = f"{uuid.uuid4()}{file_ext}" background_tasks,
file_path = os.path.join(UPLOAD_DIR, unique_filename) file,
description,
# Save file captured_at
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)
return db_evidence return db_evidence
@ -143,11 +101,10 @@ async def retry_transcription(
db.refresh(db_evidence) db.refresh(db_evidence)
# Queue transcription task # 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) background_tasks.add_task(process_transcription, db_evidence.id)
return db_evidence return db_evidence
return db_evidence
@router.put("/evidence/{evidence_id}", response_model=schemas.Evidence) @router.put("/evidence/{evidence_id}", response_model=schemas.Evidence)
def update_evidence( def update_evidence(

View File

@ -1,15 +1,15 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session 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 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 = 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)): 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() user = db.query(User).filter(User.email == form_data.username).first()
if not user or not verify_password(form_data.password, user.hashed_password): if not user or not verify_password(form_data.password, user.hashed_password):

View File

@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
from database import get_db from app.db.database import get_db
from models import Contractor from app.models.models import Contractor
from security import get_current_active_user from app.security import get_current_active_user
import schemas import app.schemas
router = APIRouter( router = APIRouter(
prefix="/contractors", prefix="/contractors",
@ -12,15 +12,15 @@ router = APIRouter(
dependencies=[Depends(get_current_active_user)] dependencies=[Depends(get_current_active_user)]
) )
@router.post("/", response_model=schemas.Contractor) @router.post("/", response_model=app.schemas.Contractor)
def create_contractor(contractor: schemas.ContractorCreate, db: Session = Depends(get_db)): def create_contractor(contractor: app.schemas.ContractorCreate, db: Session = Depends(get_db)):
db_contractor = Contractor(**contractor.dict()) db_contractor = Contractor(**contractor.dict())
db.add(db_contractor) db.add(db_contractor)
db.commit() db.commit()
db.refresh(db_contractor) db.refresh(db_contractor)
return db_contractor return db_contractor
@router.get("/", response_model=List[schemas.Contractor]) @router.get("/", response_model=List[app.schemas.Contractor])
def read_contractors( def read_contractors(
parent_id: Optional[int] = None, parent_id: Optional[int] = None,
only_parents: bool = False, only_parents: bool = False,
@ -37,18 +37,18 @@ def read_contractors(
query = query.filter(Contractor.is_active == is_active) query = query.filter(Contractor.is_active == is_active)
return query.all() 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)): def read_contractor(contractor_id: int, db: Session = Depends(get_db)):
db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first() db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first()
if not db_contractor: if not db_contractor:
raise HTTPException(status_code=404, detail="Contractor not found") raise HTTPException(status_code=404, detail="Contractor not found")
return db_contractor return db_contractor
@router.put("/{contractor_id}", response_model=schemas.Contractor) @router.put("/{contractor_id}", response_model=app.schemas.Contractor)
@router.patch("/{contractor_id}", response_model=schemas.Contractor) @router.patch("/{contractor_id}", response_model=app.schemas.Contractor)
def update_contractor( def update_contractor(
contractor_id: int, contractor_id: int,
contractor: schemas.ContractorUpdate, contractor: app.schemas.ContractorUpdate,
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first() db_contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first()

View File

@ -6,9 +6,9 @@ import datetime
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
from database import get_db from app.db.database import get_db
from models import NonConformity, Evidence from app.models.models import NonConformity, Evidence
import schemas import app.schemas
router = APIRouter( router = APIRouter(
prefix="/guest", prefix="/guest",
@ -19,7 +19,7 @@ UPLOAD_DIR = "uploads"
if not os.path.exists(UPLOAD_DIR): if not os.path.exists(UPLOAD_DIR):
os.makedirs(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( def read_guest_nc(
access_hash: str, access_hash: str,
db: Session = Depends(get_db) 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") raise HTTPException(status_code=404, detail="Non-Conformity not found or invalid link")
return db_nc 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( def update_guest_nc(
access_hash: str, access_hash: str,
nc_update: schemas.NonConformityUpdate, nc_update: app.schemas.NonConformityUpdate,
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
db_nc = db.query(NonConformity).filter(NonConformity.access_hash == access_hash).first() db_nc = db.query(NonConformity).filter(NonConformity.access_hash == access_hash).first()
@ -62,7 +62,7 @@ def update_guest_nc(
db.refresh(db_nc) db.refresh(db_nc)
return 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( async def upload_guest_evidence(
access_hash: str, access_hash: str,
file: UploadFile = File(...), file: UploadFile = File(...),

View File

@ -5,10 +5,10 @@ import datetime
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
from database import get_db from app.db.database import get_db
from models import NonConformity, User, Activity, Evidence, Contractor from app.models.models import NonConformity, User, Activity, Evidence, Contractor
from security import get_current_active_user from app.security import get_current_active_user
import schemas import app.schemas
router = APIRouter( router = APIRouter(
prefix="/non-conformities", prefix="/non-conformities",
@ -19,9 +19,9 @@ UPLOAD_DIR = "uploads"
if not os.path.exists(UPLOAD_DIR): if not os.path.exists(UPLOAD_DIR):
os.makedirs(UPLOAD_DIR) os.makedirs(UPLOAD_DIR)
@router.post("/", response_model=schemas.NonConformity) @router.post("/", response_model=app.schemas.NonConformity)
def create_nc( def create_nc(
nc: schemas.NonConformityCreate, nc: app.schemas.NonConformityCreate,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user) current_user: User = Depends(get_current_active_user)
): ):
@ -41,7 +41,7 @@ def create_nc(
db.refresh(db_nc) db.refresh(db_nc)
return db_nc return db_nc
@router.get("/", response_model=List[schemas.NonConformity]) @router.get("/", response_model=List[app.schemas.NonConformity])
def read_ncs( def read_ncs(
activity_id: Optional[int] = None, activity_id: Optional[int] = None,
status: Optional[str] = None, status: Optional[str] = None,
@ -56,7 +56,7 @@ def read_ncs(
return query.all() return query.all()
@router.get("/{nc_id}", response_model=schemas.NonConformity) @router.get("/{nc_id}", response_model=app.schemas.NonConformity)
def read_nc( def read_nc(
nc_id: int, nc_id: int,
db: Session = Depends(get_db), db: Session = Depends(get_db),
@ -67,11 +67,11 @@ def read_nc(
raise HTTPException(status_code=404, detail="Non-Conformity not found") raise HTTPException(status_code=404, detail="Non-Conformity not found")
return db_nc return db_nc
@router.put("/{nc_id}", response_model=schemas.NonConformity) @router.put("/{nc_id}", response_model=app.schemas.NonConformity)
@router.patch("/{nc_id}", response_model=schemas.NonConformity) @router.patch("/{nc_id}", response_model=app.schemas.NonConformity)
def update_nc( def update_nc(
nc_id: int, nc_id: int,
nc: schemas.NonConformityUpdate, nc: app.schemas.NonConformityUpdate,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user) current_user: User = Depends(get_current_active_user)
): ):
@ -95,7 +95,7 @@ def update_nc(
db.refresh(db_nc) db.refresh(db_nc)
return 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( async def upload_nc_evidence(
nc_id: int, nc_id: int,
file: UploadFile = File(...), file: UploadFile = File(...),
@ -175,7 +175,7 @@ def notify_responsible(
db.refresh(db_nc) db.refresh(db_nc)
# Send email # 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) EmailService.send_nc_notification(db_nc.responsible_email, db_nc.access_hash, db_nc.description)
# Update status to in-checking # Update status to in-checking

View File

@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from database import get_db from app.db.database import get_db
from models import Project, Specialty, Contractor from app.models.models import Project, Specialty, Contractor
from security import get_current_active_user from app.security import get_current_active_user
import schemas import app.schemas
router = APIRouter( router = APIRouter(
prefix="/projects", prefix="/projects",
@ -12,8 +12,8 @@ router = APIRouter(
dependencies=[Depends(get_current_active_user)] dependencies=[Depends(get_current_active_user)]
) )
@router.post("/", response_model=schemas.Project) @router.post("/", response_model=app.schemas.Project)
def create_project(project: schemas.ProjectCreate, db: Session = Depends(get_db)): def create_project(project: app.schemas.ProjectCreate, db: Session = Depends(get_db)):
db_project = db.query(Project).filter(Project.code == project.code).first() db_project = db.query(Project).filter(Project.code == project.code).first()
if db_project: if db_project:
raise HTTPException(status_code=400, detail="Project code already exists") 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) db.refresh(db_project)
return 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)): def read_projects(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
projects = db.query(Project).offset(skip).limit(limit).all() projects = db.query(Project).offset(skip).limit(limit).all()
return projects 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)): def read_project(project_id: int, db: Session = Depends(get_db)):
db_project = db.query(Project).filter(Project.id == project_id).first() db_project = db.query(Project).filter(Project.id == project_id).first()
if db_project is None: if db_project is None:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")
return db_project return db_project
@router.put("/{project_id}", response_model=schemas.Project) @router.put("/{project_id}", response_model=app.schemas.Project)
def update_project(project_id: int, project: schemas.ProjectCreate, db: Session = Depends(get_db)): 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() db_project = db.query(Project).filter(Project.id == project_id).first()
if db_project is None: if db_project is None:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")

View File

@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from database import get_db from app.db.database import get_db
from models import Specialty from app.models.models import Specialty
from security import get_current_active_user from app.security import get_current_active_user
import schemas import app.schemas
router = APIRouter( router = APIRouter(
prefix="/specialties", prefix="/specialties",
@ -12,6 +12,6 @@ router = APIRouter(
dependencies=[Depends(get_current_active_user)] 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)): def read_specialties(db: Session = Depends(get_db)):
return db.query(Specialty).all() return db.query(Specialty).all()

View File

@ -1,9 +1,9 @@
import os import os
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
from security import get_current_active_user
import google.generativeai as genai import google.generativeai as genai
import tempfile import tempfile
from dotenv import load_dotenv from dotenv import load_dotenv
from app.security import get_current_active_user
load_dotenv() load_dotenv()

View File

@ -1,11 +1,11 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from database import get_db from app.db.database import get_db
from models import User from app.models.models import User
from security import get_password_hash, get_current_active_user from app.security import get_password_hash, get_current_active_user
from services import users from app.services import users
from schemas import UserCreate, User from app.schemas import UserCreate, User
router = APIRouter( router = APIRouter(
prefix="/users", prefix="/users",

View File

@ -1,7 +1,7 @@
from pydantic import BaseModel, EmailStr, field_validator from pydantic import BaseModel, EmailStr, field_validator
from typing import Optional, List from typing import Optional, List
from datetime import datetime from datetime import datetime
from models import UserRole, ActivityType, NCLevel, NCType from app.models.models import UserRole, ActivityType, NCLevel, NCType
# Token # Token
class Token(BaseModel): class Token(BaseModel):

View File

@ -5,9 +5,9 @@ from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from database import get_db from app.db.database import get_db
from models import User from app.models.models import User
import schemas import app.schemas
# Secret key for JWT (should be in env vars in production) # Secret key for JWT (should be in env vars in production)
SECRET_KEY = "super_secret_key_for_fritos_fresh_supervision_system" 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") email: str = payload.get("sub")
if email is None: if email is None:
raise credentials_exception raise credentials_exception
token_data = schemas.TokenData(email=email) token_data = app.schemas.TokenData(email=email)
except JWTError: except JWTError:
raise credentials_exception raise credentials_exception
user = db.query(User).filter(User.email == token_data.email).first() user = db.query(User).filter(User.email == token_data.email).first()

Binary file not shown.

View File

@ -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

View File

@ -1,9 +1,9 @@
import os import os
import time import time
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from models import Evidence
import google.generativeai as genai 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): def process_transcription(evidence_id: int):
""" """

View File

@ -1,8 +1,8 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from models import User
from security import get_password_hash
from schemas import UserCreate
from fastapi import HTTPException 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): def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(User).offset(skip).limit(limit).all() return db.query(User).offset(skip).limit(limit).all()

View File

@ -11,7 +11,7 @@ if sys.version_info < (3, 10):
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware 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 import os
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles

View File

@ -1,24 +1,22 @@
alembic==1.16.5 alembic==1.17.2
annotated-doc==0.0.4 annotated-doc==0.0.4
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.12.0 anyio==4.12.0
bcrypt==3.2.2 bcrypt==5.0.0
cachetools==6.2.4 cachetools==6.2.4
certifi==2025.11.12 certifi==2025.11.12
cffi==2.0.0 cffi==2.0.0
charset-normalizer==3.4.4 charset-normalizer==3.4.4
click==8.1.8 click==8.3.1
cryptography==46.0.3 cryptography==46.0.3
dnspython==2.7.0 dnspython==2.8.0
ecdsa==0.19.1 ecdsa==0.19.1
email-validator==2.3.0 email-validator==2.3.0
exceptiongroup==1.3.1 exceptiongroup==1.3.1
fastapi==0.125.0 fastapi==0.128.0
fastapi-cli==0.0.16 fastapi-cli==0.0.20
fastapi-cloud-cli==0.7.0
fastar==0.8.0
google-ai-generativelanguage==0.6.15 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-api-python-client==2.187.0
google-auth==2.45.0 google-auth==2.45.0
google-auth-httplib2==0.3.0 google-auth-httplib2==0.3.0
@ -32,18 +30,12 @@ httplib2==0.31.0
httptools==0.7.1 httptools==0.7.1
httpx==0.28.1 httpx==0.28.1
idna==3.11 idna==3.11
iniconfig==2.1.0
Jinja2==3.1.6
Mako==1.3.10 Mako==1.3.10
markdown-it-py==3.0.0 markdown-it-py==4.0.0
MarkupSafe==3.0.3 MarkupSafe==3.0.3
mdurl==0.1.2 mdurl==0.1.2
numpy==2.0.2 numpy==2.2.6
opencv-python-headless==4.12.0.88 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 proto-plus==1.27.0
protobuf==5.29.5 protobuf==5.29.5
psycopg2-binary==2.9.11 psycopg2-binary==2.9.11
@ -53,30 +45,26 @@ pycparser==2.23
pydantic==2.12.5 pydantic==2.12.5
pydantic_core==2.41.5 pydantic_core==2.41.5
Pygments==2.19.2 Pygments==2.19.2
pyparsing==3.2.5 pyparsing==3.3.1
pytest==8.4.2
python-dotenv==1.2.1 python-dotenv==1.2.1
python-jose==3.5.0 python-jose==3.5.0
python-multipart==0.0.20 python-multipart==0.0.21
PyYAML==6.0.3 PyYAML==6.0.3
requests==2.32.5 requests==2.32.5
rich==14.2.0 rich==14.2.0
rich-toolkit==0.17.1 rich-toolkit==0.17.1
rignore==0.7.6
rsa==4.9.1 rsa==4.9.1
sentry-sdk==2.48.0
shellingham==1.5.4 shellingham==1.5.4
six==1.17.0 six==1.17.0
SQLAlchemy==2.0.45 SQLAlchemy==2.0.45
starlette==0.49.3 starlette==0.50.0
tomli==2.3.0
tqdm==4.67.1 tqdm==4.67.1
typer==0.20.0 typer==0.21.0
typing-inspection==0.4.2 typing-inspection==0.4.2
typing_extensions==4.15.0 typing_extensions==4.15.0
uritemplate==4.2.0 uritemplate==4.2.0
urllib3==2.6.2 urllib3==2.6.2
uvicorn==0.38.0 uvicorn==0.40.0
uvloop==0.22.1 uvloop==0.22.1
watchfiles==1.1.1 watchfiles==1.1.1
websockets==15.0.1 websockets==15.0.1

View File

@ -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

View File

@ -1,3 +1,3 @@
export const environment = { export const environment = {
apiUrl: 'http://192.168.1.74:8000' apiUrl: 'http://192.168.1.76:8000'
}; };

View File

@ -1,3 +1,3 @@
export const environment = { export const environment = {
apiUrl: 'http://192.168.1.74:8000' apiUrl: 'http://192.168.1.76:8000'
}; };