From addc9035e15bde3dd3e865c1ac22ca59259f5b6c Mon Sep 17 00:00:00 2001 From: codinBabe Date: Sun, 27 Oct 2024 08:22:24 +0100 Subject: [PATCH 1/3] Feat:made corrections and created services folder to handle diff services, makng the routes cleaner --- backend/app/api/v1/auth.py | 14 ++--- backend/app/api/v1/cipher.py | 82 ++++--------------------- backend/app/api/v1/file.py | 16 +++++ backend/app/api/v1/file_handler.py | 38 ------------ backend/app/core/config.py | 1 + backend/app/core/security.py | 3 +- backend/app/crud/document_crud.py | 2 +- backend/app/crud/user_crud.py | 25 +++----- backend/app/main.py | 4 +- backend/app/models/document_model.py | 14 ++--- backend/app/requirements.txt | 16 +++++ backend/app/schemas/ceaser_schema.py | 16 ----- backend/app/schemas/document_schema.py | 9 +-- backend/app/services/__init__.py | 0 backend/app/services/auth_service.py | 18 ++++++ backend/app/services/file_service.py | 25 ++++++++ backend/app/services/process_service.py | 71 +++++++++++++++++++++ backend/app/services/user_service.py | 23 +++++++ 18 files changed, 213 insertions(+), 164 deletions(-) create mode 100644 backend/app/api/v1/file.py delete mode 100644 backend/app/api/v1/file_handler.py delete mode 100644 backend/app/schemas/ceaser_schema.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/auth_service.py create mode 100644 backend/app/services/file_service.py create mode 100644 backend/app/services/process_service.py create mode 100644 backend/app/services/user_service.py diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index 3477263..870321b 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -1,16 +1,15 @@ -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session from db.session import get_db -from core.security import create_access_token -from crud.user_crud import authenticate_user, create_user +from services.auth_service import register_user, login_user from schemas.user_schema import UserCreate router = APIRouter() @router.post("/register") def register(user_data:UserCreate, db: Session = Depends(get_db)): - user = create_user(db, user_data) + user = register_user(db, user_data) if not user: raise HTTPException(status_code=400, detail="Email already registered") return {"message": "User created successfully"} @@ -18,8 +17,7 @@ def register(user_data:UserCreate, db: Session = Depends(get_db)): @router.post("/login") def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): - user = authenticate_user(db, form_data.username, form_data.password) - if not user: + token = login_user(db, form_data.username, form_data.password) + if not token: raise HTTPException(status_code=401, detail="Incorrect email or password") - access_token = create_access_token(user_id=user.id, data={"user_id": user.id}) - return {"access_token": access_token, "token_type": "bearer"} + return {"access_token": token, "token_type": "bearer"} diff --git a/backend/app/api/v1/cipher.py b/backend/app/api/v1/cipher.py index d664605..ee239a1 100644 --- a/backend/app/api/v1/cipher.py +++ b/backend/app/api/v1/cipher.py @@ -1,76 +1,16 @@ -from fastapi import APIRouter, HTTPException, Depends -from sqlalchemy.orm import Session -from db.session import get_db -from models.document_model import Document -from schemas.document_schema import DocumentCreate -from crud.document_crud import create_document -from core.security import get_user_id_from_token -from .tasks import encrypt_file, decrypt_file -from celery.result import AsyncResult -import os +from fastapi import APIRouter +from services.process_service import encrypt_uploaded_file, decrypt_uploaded_file, get_task_status router = APIRouter() -@router.post("/encrypt_file") -async def encrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): - user_id = get_user_id_from_token(token) - if not os.path.exists(file_path): - raise HTTPException(status_code=404, detail="File not found") - - if token: - task = encrypt_file.delay(file_path, shift) - document = DocumentCreate( - filename=os.path.basename(file_path), - content=open(file_path, "rb").read(), - status="encrypting", - task_id=task.id, - user_id=user_id, - ) - create_document(db, document) - return {"message": "File encryption started", "task_id": task.id} - else: - task = encrypt_file.delay(file_path, shift) - return {"message": "File encryption started", "task_id": task.id} +@router.post("/encrypt") +def encrypt_file_route(file_path: str, shift: int, token: str): + return encrypt_uploaded_file(file_path, shift, token) +@router.post("/decrypt") +def decrypt_file_route(file_path: str, shift: int, token: str): + return decrypt_uploaded_file(file_path, shift, token) -@router.post("/decrypt_file") -async def decrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): - user_id = get_user_id_from_token(token) - if not os.path.exists(file_path): - raise HTTPException(status_code=404, detail="File not found") - - if token: - task = decrypt_file.delay(file_path, shift) - document = DocumentCreate( - filename=os.path.basename(file_path), - content=open(file_path, "rb").read(), - status="decrypting", - user_id=user_id, - task_id=task.id - ) - create_document(db, document) - return {"message": "File decryption started", "task_id": task.id} - else: - task = decrypt_file.delay(file_path, shift) - return {"message": "File decryption started", "task_id": task.id} - - -@router.get("/task_status/{task_id}") -async def task_status(task_id: str, db: Session = Depends(get_db)): - task = AsyncResult(task_id) - if task.state == "PENDING": - return {"status": "pending", "details": "Task is being processed"} - elif task.state == "SUCCESS": - file_path = task.result - file_name = os.path.basename(file_path) - - document = db.query(Document).filter(Document.task_id == task_id).first() - if document: - document.status = "completed" - db.commit() - - return {"status": "success", "details": "Task completed", "file_name": file_name} - elif task.state == "FAILURE": - return {"status": "failure", "details": str(task.info)} - else: - return {"status": task.state} \ No newline at end of file +@router.get("/status/{task_id}") +def get_task_status_route(task_id: str): + return get_task_status(task_id) \ No newline at end of file diff --git a/backend/app/api/v1/file.py b/backend/app/api/v1/file.py new file mode 100644 index 0000000..0095fed --- /dev/null +++ b/backend/app/api/v1/file.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter, File, UploadFile +from services.file_service import upload_file, download_file, delete_file + +router = APIRouter() + +@router.post("/upload") +def upload_file_route(file: UploadFile = File(...)): + return upload_file(file) + +@router.get("/download/{file_name}") +def download_file_route(file_name: str): + return download_file(file_name) + +@router.delete("/delete/{file_name}") +def delete_file_route(file_name: str): + return delete_file(file_name) \ No newline at end of file diff --git a/backend/app/api/v1/file_handler.py b/backend/app/api/v1/file_handler.py deleted file mode 100644 index 40e89a5..0000000 --- a/backend/app/api/v1/file_handler.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import APIRouter, File, UploadFile, HTTPException -from fastapi.responses import FileResponse -import os - -router = APIRouter() - -TEMP_DIR = "temp" - -if not os.path.exists(TEMP_DIR): - os.makedirs(TEMP_DIR) - - -def get_media_type(file_path: str): - """Get media type based on file extension""" - if file_path.endswith(".pdf"): - return "application/pdf" - elif file_path.endswith(".txt"): - return "text/plain" - elif file_path.endswith(".csv"): - return "text/csv" - return "application/octet-stream" - - -@router.post("/upload_file") -async def upload_file(file: UploadFile = File(...)): - file_path = os.path.join(TEMP_DIR, file.filename) - with open(file_path, "wb") as buffer: - buffer.write(file.file.read()) - return {"message": "File uploaded successfully", "file_path": file_path} - - -@router.get("/download_file/{file_name}") -async def download_file(file_name: str): - file_path = os.path.join(TEMP_DIR, file_name) - if not os.path.exists(file_path): - raise HTTPException(status_code=404, detail="File not found") - media_type = get_media_type(file_path) - return FileResponse(file_path, media_type=media_type, filename=file_name) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 66388b1..38d07cc 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -13,5 +13,6 @@ class Setting(BaseSettings): DATABASE_URL: str CELERY_BROKER_URL: str CELERY_RESULT_BACKEND: str + GCS_BUCKET_NAME: str settings = Setting() \ No newline at end of file diff --git a/backend/app/core/security.py b/backend/app/core/security.py index cdff7c4..77c6164 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -38,6 +38,7 @@ def verify_token(token: str): except Exception as e: return False + def get_user_id_from_token(token: str): payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) - return payload.get("user_id") \ No newline at end of file + return payload.get("user_id") diff --git a/backend/app/crud/document_crud.py b/backend/app/crud/document_crud.py index 5728357..a5294b6 100644 --- a/backend/app/crud/document_crud.py +++ b/backend/app/crud/document_crud.py @@ -5,7 +5,7 @@ def create_document(db: Session, document: document_schema.DocumentCreate): db_document = document_model.Document( filename=document.filename, - content=document.content, + storage_path=document.storage_path, status=document.status, task_id=document.task_id, user_id=document.user_id diff --git a/backend/app/crud/user_crud.py b/backend/app/crud/user_crud.py index e7dea84..3862210 100644 --- a/backend/app/crud/user_crud.py +++ b/backend/app/crud/user_crud.py @@ -1,27 +1,20 @@ from sqlalchemy.orm import Session -from fastapi import HTTPException -from core import security -from models import user_model -from schemas import user_schema +from models.user_model import User +from schemas.user_schema import UserCreate -def create_user(db: Session, user: user_schema.UserCreate): - hashed_password = security.get_password_hash(user.password) - db_user = user_model.User(username=user.username, email=user.email, password=hashed_password) +def create_user(db: Session, user: UserCreate): + db_user = User(email=user.email) db.add(db_user) db.commit() db.refresh(db_user) return db_user -def authenticate_user(db: Session, email: str, password: str): - user = db.query(user_model.User).filter(user_model.User.email == email).first() - if not user or not security.verify_password(password, user.password): - return False - return user def get_user(db: Session, user_id: int): - return db.query(user_model.User).filter(user_model.User.id == user_id).first() + return db.query(User).filter(User.id == user_id).first() -def get_current_user(db: Session, token: str): - user_id = security.get_user_id_from_token(token) - return get_user(db, user_id) \ No newline at end of file +def delete_user(db: Session, user_id: int): + db.query(User).filter(User.id == user_id).delete() + db.commit() + return \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index aec005b..9cd707e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,7 +1,7 @@ from fastapi import FastAPI from db.session import engine from db.base import Base -from api.v1 import auth, cipher, file_handler +from api.v1 import auth, cipher, file app = FastAPI() @@ -9,7 +9,7 @@ app.include_router(auth.router) app.include_router(cipher.router) -app.include_router(file_handler.router) +app.include_router(file.router) Base.metadata.create_all(bind=engine) diff --git a/backend/app/models/document_model.py b/backend/app/models/document_model.py index 3da455a..61e4e6b 100644 --- a/backend/app/models/document_model.py +++ b/backend/app/models/document_model.py @@ -5,14 +5,14 @@ class Document(Base): __tablename__ = "documents" - + id = Column(Integer, primary_key= True, nullable=False) user_id = Column(Integer, ForeignKey("users.id")) - filename = Column(String,index=True) - content = Column(LargeBinary, default=None) - status = Column(String, nullable=False) - task_id = Column(String, nullable=False) - uploaded_at = Column(DateTime, default=datetime.now(timezone.utc)) + title = Column(String, nullable=False) + original_document_url = Column(String, nullable=False) + processed_document_url = Column(String, nullable=False) + date_created = Column(DateTime, default=datetime.now(timezone.utc)) + date_updated = Column(DateTime, default=datetime.now(timezone.utc)) - user = relationship("User", back_populates="documents") \ No newline at end of file + user = relationship("User", back_populates="documents") diff --git a/backend/app/requirements.txt b/backend/app/requirements.txt index 6789490..0fdf5ac 100644 --- a/backend/app/requirements.txt +++ b/backend/app/requirements.txt @@ -3,10 +3,12 @@ annotated-types==0.7.0 anyio==4.6.0 bcrypt==3.2.0 billiard==4.2.1 +cachetools==5.5.0 celery==5.4.0 certifi==2024.8.30 cffi==1.17.1 chardet==5.2.0 +charset-normalizer==3.4.0 click==8.1.7 click-didyoumean==0.3.1 click-plugins==1.1.1 @@ -16,6 +18,13 @@ dnspython==2.7.0 email_validator==2.2.0 fastapi==0.115.0 fastapi-cli==0.0.5 +google-api-core==2.21.0 +google-auth==2.35.0 +google-cloud-core==2.4.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-resumable-media==2.7.2 +googleapis-common-protos==1.65.0 greenlet==3.1.1 h11==0.14.0 httpcore==1.0.6 @@ -30,6 +39,10 @@ mdurl==0.1.2 passlib==1.7.4 pillow==10.4.0 prompt_toolkit==3.0.48 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 pycparser==2.22 pydantic==2.9.2 pydantic-settings==2.5.2 @@ -43,7 +56,9 @@ python-multipart==0.0.12 PyYAML==6.0.2 redis==5.1.1 reportlab==4.2.5 +requests==2.32.3 rich==13.9.2 +rsa==4.9 shellingham==1.5.4 six==1.16.0 sniffio==1.3.1 @@ -52,6 +67,7 @@ starlette==0.38.6 typer==0.12.5 typing_extensions==4.12.2 tzdata==2024.2 +urllib3==2.2.3 uvicorn==0.31.0 vine==5.1.0 watchfiles==0.24.0 diff --git a/backend/app/schemas/ceaser_schema.py b/backend/app/schemas/ceaser_schema.py deleted file mode 100644 index e9a41cf..0000000 --- a/backend/app/schemas/ceaser_schema.py +++ /dev/null @@ -1,16 +0,0 @@ -from pydantic import BaseModel - -class CeaserCipherBase(BaseModel): - """Ceaser Cipher Model""" - text : str - shift : int - -class CeaserCipherCreate(CeaserCipherBase): - """extends CeaserCipherBase""" - decrypt : bool - -class CeaserCipherResponse(CeaserCipherBase): - id: int - - class Config: - from_attributes = True \ No newline at end of file diff --git a/backend/app/schemas/document_schema.py b/backend/app/schemas/document_schema.py index 114b808..c46cce2 100644 --- a/backend/app/schemas/document_schema.py +++ b/backend/app/schemas/document_schema.py @@ -2,10 +2,11 @@ class DocumentBase(BaseModel): """Document Model""" - filename : str - content : bytes - status : str - task_id : str + title : str + original_document_url : str + processed_document_url : str + date_created : str + date_updated : str class DocumentCreate(DocumentBase): """extends DocumentBase""" diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py new file mode 100644 index 0000000..c487d84 --- /dev/null +++ b/backend/app/services/auth_service.py @@ -0,0 +1,18 @@ +from core.security import create_access_token +from crud.user_crud import create_user +from services.user_service import authenticate_user + + +def register_user(db, user_data): + user = create_user(db, user_data) + if not user: + return None + return user + + +def login_user(db, username, password): + user = authenticate_user(db, username, password) + if not user: + return None + token = create_access_token(user_id=user.id, data={"user_id": user.id}) + return token diff --git a/backend/app/services/file_service.py b/backend/app/services/file_service.py new file mode 100644 index 0000000..6edc642 --- /dev/null +++ b/backend/app/services/file_service.py @@ -0,0 +1,25 @@ +from fastapi import File, UploadFile, HTTPException +from google.cloud import storage +from app.core.config import settings + +storage_client = storage.Client() +bucket = storage_client.bucket(settings.GCS_BUCKET_NAME) + + +def upload_file(file: UploadFile = File(...)): + blob = bucket.blob(file.filename) + blob.upload_from_file(file.file) + return {"message": "File uploaded successfully", "file_path": file.filename} + +def download_file(file_name: str): + blob = bucket.blob(file_name) + if not blob.exists(): + raise HTTPException(status_code=404, detail="File not found") + return blob.download_as_bytes() + +def delete_file(file_name: str): + blob = bucket.blob(file_name) + if not blob.exists(): + raise HTTPException(status_code=404, detail="File not found") + blob.delete() + return {"message": "File deleted successfully"} \ No newline at end of file diff --git a/backend/app/services/process_service.py b/backend/app/services/process_service.py new file mode 100644 index 0000000..cee03ed --- /dev/null +++ b/backend/app/services/process_service.py @@ -0,0 +1,71 @@ +from fastapi import HTTPException, Depends +from sqlalchemy.orm import Session +from celery.result import AsyncResult +from google.cloud import storage +from db.session import get_db +from schemas.document_schema import DocumentCreate +from crud.document_crud import create_document +from core.security import get_user_id_from_token +from core.config import settings +from api.v1.tasks import encrypt_file, decrypt_file + + +storage_client = storage.Client() +bucket = storage_client.bucket(settings.GCS_BUCKET_NAME) + +def encrypt_uploaded_file(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): + user_id = get_user_id_from_token(token) + blob = bucket.blob(file_path) + if not blob.exists(): + raise HTTPException(status_code=404, detail="File not found") + + if token: + task = encrypt_file.delay(file_path, shift) + document = DocumentCreate( + title=blob.name, + original_document_url=file_path, + processed_document_url=blob.public_url, + user_id=user_id, + date_created=blob.time_created, + date_updated=blob.updated, + ) + create_document(db, document) + return {"message": "File encryption started", "task_id": task.id} + else: + task = encrypt_file.delay(file_path, shift) + return {"message": "File encryption started", "task_id": task.id} + + +def decrypt_uploaded_file(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): + user_id = get_user_id_from_token(token) + blob = bucket.blob(file_path) + if not blob.exists(): + raise HTTPException(status_code=404, detail="File not found") + + if token: + task = decrypt_file.delay(file_path, shift) + document = DocumentCreate( + title=blob.name, + original_document_url=file_path, + processed_document_url=blob.public_url, + user_id=user_id, + date_created=blob.time_created, + date_updated=blob.updated, + ) + create_document(db, document) + return {"message": "File decryption started", "task_id": task.id} + else: + task = decrypt_file.delay(file_path, shift) + return {"message": "File decryption started", "task_id": task.id} + + +def get_task_status(task_id: str): + task = AsyncResult(task_id) + if task.state == "PENDING": + return {"status": "pending", "details": "Task is being processed"} + elif task.state == "SUCCESS": + file_path = task.result + return {"status": "completed", "details": file_path} + else: + return {"status": "failed", "details": "Task failed"} + \ No newline at end of file diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py new file mode 100644 index 0000000..4e16be2 --- /dev/null +++ b/backend/app/services/user_service.py @@ -0,0 +1,23 @@ +from sqlalchemy.orm import Session +from core.security import verify_password, get_password_hash, get_user_id_from_token +from models.user_model import User +from schemas.user_schema import UserCreate +from crud.user_crud import create_user, get_user + + +def create_user_with_password(db: Session, user: UserCreate): + hashed_password = get_password_hash(user.password) + user.password = hashed_password + return create_user(db, user) + + +def authenticate_user(db: Session, email: str, password: str): + user = db.query(User).filter(User.email == email).first() + if not user or not verify_password(password, user.password): + return False + return user + + +def get_current_user(db: Session, token: str): + user_id = get_user_id_from_token(token) + return get_user(db, user_id) \ No newline at end of file From 9abd8d6feede2387000578e5ef5cb3e4a872cf8f Mon Sep 17 00:00:00 2001 From: codinBabe Date: Tue, 29 Oct 2024 23:43:45 +0100 Subject: [PATCH 2/3] setup cloud upload storage --- backend/app/api/v1/cipher.py | 12 +++-- backend/app/core/ceaser_cipher.py | 2 - backend/app/core/firebase_init.py | 9 ++++ backend/app/crud/document_crud.py | 11 +++-- backend/app/crud/user_crud.py | 2 +- backend/app/models/document_model.py | 3 +- backend/app/requirements.txt | 12 +++++ backend/app/schemas/document_schema.py | 6 ++- backend/app/services/auth_service.py | 10 ++-- backend/app/services/file_service.py | 14 +++--- backend/app/services/process_service.py | 62 ++++++++++++++----------- 11 files changed, 88 insertions(+), 55 deletions(-) create mode 100644 backend/app/core/firebase_init.py diff --git a/backend/app/api/v1/cipher.py b/backend/app/api/v1/cipher.py index ee239a1..77c3052 100644 --- a/backend/app/api/v1/cipher.py +++ b/backend/app/api/v1/cipher.py @@ -1,15 +1,17 @@ -from fastapi import APIRouter +from sqlalchemy.orm import Session +from fastapi import APIRouter, Depends from services.process_service import encrypt_uploaded_file, decrypt_uploaded_file, get_task_status +from db.session import get_db router = APIRouter() @router.post("/encrypt") -def encrypt_file_route(file_path: str, shift: int, token: str): - return encrypt_uploaded_file(file_path, shift, token) +def encrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): + return encrypt_uploaded_file(file_path, shift, token, db) @router.post("/decrypt") -def decrypt_file_route(file_path: str, shift: int, token: str): - return decrypt_uploaded_file(file_path, shift, token) +def decrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): + return decrypt_uploaded_file(file_path, shift, token, db) @router.get("/status/{task_id}") def get_task_status_route(task_id: str): diff --git a/backend/app/core/ceaser_cipher.py b/backend/app/core/ceaser_cipher.py index 1a3a914..4dc093d 100644 --- a/backend/app/core/ceaser_cipher.py +++ b/backend/app/core/ceaser_cipher.py @@ -126,5 +126,3 @@ def caesar_cipher(data, shift, decrypt=False): else: result.append(cryptify_string(data, shift)) return result - - diff --git a/backend/app/core/firebase_init.py b/backend/app/core/firebase_init.py new file mode 100644 index 0000000..8c629ad --- /dev/null +++ b/backend/app/core/firebase_init.py @@ -0,0 +1,9 @@ +import firebase_admin +from firebase_admin import credentials + + +cred = credentials.Certificate("core/firebase-adminsdk.json") +firebase_admin.initialize_app(cred) + + + diff --git a/backend/app/crud/document_crud.py b/backend/app/crud/document_crud.py index a5294b6..a1b2969 100644 --- a/backend/app/crud/document_crud.py +++ b/backend/app/crud/document_crud.py @@ -4,11 +4,12 @@ def create_document(db: Session, document: document_schema.DocumentCreate): db_document = document_model.Document( - filename=document.filename, - storage_path=document.storage_path, - status=document.status, - task_id=document.task_id, - user_id=document.user_id + title=document.title, + original_document_url=document.original_document_url, + processed_document_url=document.processed_document_url, + user_id=document.user_id, + date_created=document.date_created, + date_updated=document.date_updated ) db.add(db_document) db.commit() diff --git a/backend/app/crud/user_crud.py b/backend/app/crud/user_crud.py index 3862210..9f50f00 100644 --- a/backend/app/crud/user_crud.py +++ b/backend/app/crud/user_crud.py @@ -3,7 +3,7 @@ from schemas.user_schema import UserCreate def create_user(db: Session, user: UserCreate): - db_user = User(email=user.email) + db_user = User(username=user.username, email=user.email, password=user.password) db.add(db_user) db.commit() db.refresh(db_user) diff --git a/backend/app/models/document_model.py b/backend/app/models/document_model.py index 61e4e6b..40929b1 100644 --- a/backend/app/models/document_model.py +++ b/backend/app/models/document_model.py @@ -3,6 +3,7 @@ from sqlalchemy import String, Column, Integer, ForeignKey, DateTime, LargeBinary from sqlalchemy.orm import relationship + class Document(Base): __tablename__ = "documents" @@ -13,6 +14,6 @@ class Document(Base): original_document_url = Column(String, nullable=False) processed_document_url = Column(String, nullable=False) date_created = Column(DateTime, default=datetime.now(timezone.utc)) - date_updated = Column(DateTime, default=datetime.now(timezone.utc)) + date_updated = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) user = relationship("User", back_populates="documents") diff --git a/backend/app/requirements.txt b/backend/app/requirements.txt index 0fdf5ac..0fc70d0 100644 --- a/backend/app/requirements.txt +++ b/backend/app/requirements.txt @@ -3,6 +3,7 @@ annotated-types==0.7.0 anyio==4.6.0 bcrypt==3.2.0 billiard==4.2.1 +CacheControl==0.14.0 cachetools==5.5.0 celery==5.4.0 certifi==2024.8.30 @@ -14,20 +15,28 @@ click-didyoumean==0.3.1 click-plugins==1.1.1 click-repl==0.3.0 colorama==0.4.6 +cryptography==43.0.3 dnspython==2.7.0 email_validator==2.2.0 fastapi==0.115.0 fastapi-cli==0.0.5 +firebase-admin==6.5.0 google-api-core==2.21.0 +google-api-python-client==2.149.0 google-auth==2.35.0 +google-auth-httplib2==0.2.0 google-cloud-core==2.4.1 +google-cloud-firestore==2.19.0 google-cloud-storage==2.18.2 google-crc32c==1.6.0 google-resumable-media==2.7.2 googleapis-common-protos==1.65.0 greenlet==3.1.1 +grpcio==1.67.1 +grpcio-status==1.67.1 h11==0.14.0 httpcore==1.0.6 +httplib2==0.22.0 httptools==0.6.1 httpx==0.27.2 idna==3.10 @@ -36,6 +45,7 @@ kombu==5.4.2 markdown-it-py==3.0.0 MarkupSafe==2.1.5 mdurl==0.1.2 +msgpack==1.1.0 passlib==1.7.4 pillow==10.4.0 prompt_toolkit==3.0.48 @@ -49,6 +59,7 @@ pydantic-settings==2.5.2 pydantic_core==2.23.4 Pygments==2.18.0 PyJWT==2.9.0 +pyparsing==3.2.0 pypdf==5.0.1 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 @@ -67,6 +78,7 @@ starlette==0.38.6 typer==0.12.5 typing_extensions==4.12.2 tzdata==2024.2 +uritemplate==4.1.1 urllib3==2.2.3 uvicorn==0.31.0 vine==5.1.0 diff --git a/backend/app/schemas/document_schema.py b/backend/app/schemas/document_schema.py index c46cce2..84827d5 100644 --- a/backend/app/schemas/document_schema.py +++ b/backend/app/schemas/document_schema.py @@ -1,12 +1,14 @@ from pydantic import BaseModel +from typing import Optional +from datetime import datetime class DocumentBase(BaseModel): """Document Model""" title : str original_document_url : str processed_document_url : str - date_created : str - date_updated : str + date_created : Optional[datetime] + date_updated : Optional[datetime] class DocumentCreate(DocumentBase): """extends DocumentBase""" diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index c487d84..7352e1b 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -1,16 +1,16 @@ +from sqlalchemy.orm import Session from core.security import create_access_token -from crud.user_crud import create_user -from services.user_service import authenticate_user +from services.user_service import authenticate_user, create_user_with_password -def register_user(db, user_data): - user = create_user(db, user_data) +def register_user(db: Session, user_data): + user = create_user_with_password(db, user_data) if not user: return None return user -def login_user(db, username, password): +def login_user(db: Session, username, password): user = authenticate_user(db, username, password) if not user: return None diff --git a/backend/app/services/file_service.py b/backend/app/services/file_service.py index 6edc642..c734cce 100644 --- a/backend/app/services/file_service.py +++ b/backend/app/services/file_service.py @@ -1,21 +1,23 @@ from fastapi import File, UploadFile, HTTPException -from google.cloud import storage -from app.core.config import settings +from firebase_admin import storage +from core import firebase_init +from core.config import settings -storage_client = storage.Client() -bucket = storage_client.bucket(settings.GCS_BUCKET_NAME) +bucket = storage.bucket(settings.GCS_BUCKET_NAME) def upload_file(file: UploadFile = File(...)): blob = bucket.blob(file.filename) blob.upload_from_file(file.file) - return {"message": "File uploaded successfully", "file_path": file.filename} + return {"message": "File uploaded successfully", "file_path": blob.public_url, "file_name": blob.name} + def download_file(file_name: str): blob = bucket.blob(file_name) if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") - return blob.download_as_bytes() + return blob.public_url + def delete_file(file_name: str): blob = bucket.blob(file_name) diff --git a/backend/app/services/process_service.py b/backend/app/services/process_service.py index cee03ed..6127b99 100644 --- a/backend/app/services/process_service.py +++ b/backend/app/services/process_service.py @@ -1,63 +1,69 @@ -from fastapi import HTTPException, Depends +from fastapi import HTTPException from sqlalchemy.orm import Session from celery.result import AsyncResult -from google.cloud import storage -from db.session import get_db +from firebase_admin import storage from schemas.document_schema import DocumentCreate from crud.document_crud import create_document from core.security import get_user_id_from_token from core.config import settings +from core import firebase_init from api.v1.tasks import encrypt_file, decrypt_file +from datetime import datetime, timezone -storage_client = storage.Client() -bucket = storage_client.bucket(settings.GCS_BUCKET_NAME) +bucket = storage.bucket(settings.GCS_BUCKET_NAME) -def encrypt_uploaded_file(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): - user_id = get_user_id_from_token(token) +def encrypt_uploaded_file( + file_path: str, + shift: int, + token: str, + db: Session +): + user_id = get_user_id_from_token(token) if token else None blob = bucket.blob(file_path) if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") - if token: - task = encrypt_file.delay(file_path, shift) + task = encrypt_file.delay(file_path, shift) + if token and user_id: document = DocumentCreate( title=blob.name, original_document_url=file_path, processed_document_url=blob.public_url, user_id=user_id, - date_created=blob.time_created, - date_updated=blob.updated, - ) + date_created=datetime.now(timezone.utc), + date_updated=datetime.now(timezone.utc), + ) create_document(db, document) - return {"message": "File encryption started", "task_id": task.id} - else: - task = encrypt_file.delay(file_path, shift) - return {"message": "File encryption started", "task_id": task.id} + + return {"message": "File encryption started", "task_id": task.id} -def decrypt_uploaded_file(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): - user_id = get_user_id_from_token(token) +def decrypt_uploaded_file( + file_path: str, + shift: int, + token: str, + db: Session +): + user_id = get_user_id_from_token(token) if token else None blob = bucket.blob(file_path) if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") - if token: - task = decrypt_file.delay(file_path, shift) + task = decrypt_file.delay(file_path, shift) + if token and user_id: document = DocumentCreate( title=blob.name, original_document_url=file_path, processed_document_url=blob.public_url, user_id=user_id, - date_created=blob.time_created, - date_updated=blob.updated, - ) + date_created=datetime.now(timezone.utc), + date_updated=datetime.now(timezone.utc), + ) create_document(db, document) - return {"message": "File decryption started", "task_id": task.id} - else: - task = decrypt_file.delay(file_path, shift) - return {"message": "File decryption started", "task_id": task.id} - + + return {"message": "File decryption started", "task_id": task.id} + def get_task_status(task_id: str): task = AsyncResult(task_id) From 2f313722f54e3b9e2e2ddf731d4f5310805f049c Mon Sep 17 00:00:00 2001 From: codinBabe Date: Thu, 31 Oct 2024 11:20:24 +0100 Subject: [PATCH 3/3] feat: tested all route and works well --- backend/app/api/v1/auth.py | 2 + backend/app/api/v1/cipher.py | 23 ++++-- backend/app/api/v1/file.py | 33 ++++++-- backend/app/api/v1/tasks.py | 52 +++++++----- backend/app/core/ceaser_cipher.py | 88 +++++++------------- backend/app/crud/document_crud.py | 2 +- backend/app/main.py | 1 + backend/app/models/document_model.py | 4 +- backend/app/schemas/document_schema.py | 2 +- backend/app/services/file_service.py | 54 ++++++++++--- backend/app/services/process_service.py | 102 ++++++++++++++---------- 11 files changed, 215 insertions(+), 148 deletions(-) diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index 870321b..e0d18e8 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -9,6 +9,7 @@ @router.post("/register") def register(user_data:UserCreate, db: Session = Depends(get_db)): + """Route to register a new user.""" user = register_user(db, user_data) if not user: raise HTTPException(status_code=400, detail="Email already registered") @@ -17,6 +18,7 @@ def register(user_data:UserCreate, db: Session = Depends(get_db)): @router.post("/login") def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + """Route to login a user.""" token = login_user(db, form_data.username, form_data.password) if not token: raise HTTPException(status_code=401, detail="Incorrect email or password") diff --git a/backend/app/api/v1/cipher.py b/backend/app/api/v1/cipher.py index 77c3052..5cc581d 100644 --- a/backend/app/api/v1/cipher.py +++ b/backend/app/api/v1/cipher.py @@ -1,18 +1,31 @@ from sqlalchemy.orm import Session -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Query from services.process_service import encrypt_uploaded_file, decrypt_uploaded_file, get_task_status from db.session import get_db router = APIRouter() @router.post("/encrypt") -def encrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): +def encrypt_file_route( + file_path: str, + shift: int, + token: str = Query(None), + db: Session = Depends(get_db) +): + """Route to encrypt a file using Caesar cipher.""" return encrypt_uploaded_file(file_path, shift, token, db) @router.post("/decrypt") -def decrypt_file_route(file_path: str, shift: int, token: str, db: Session = Depends(get_db)): +def decrypt_file_route( + file_path: str, + shift: int, + token: str = Query(None), + db: Session = Depends(get_db) +): + """Route to decrypt a file using Caesar cipher.""" return decrypt_uploaded_file(file_path, shift, token, db) @router.get("/status/{task_id}") -def get_task_status_route(task_id: str): - return get_task_status(task_id) \ No newline at end of file +def get_task_status_route(task_id: str, db: Session = Depends(get_db)): + """Route to get the status of a task.""" + return get_task_status(task_id, db) diff --git a/backend/app/api/v1/file.py b/backend/app/api/v1/file.py index 0095fed..28beb96 100644 --- a/backend/app/api/v1/file.py +++ b/backend/app/api/v1/file.py @@ -1,16 +1,35 @@ -from fastapi import APIRouter, File, UploadFile +from fastapi import APIRouter, File, UploadFile, HTTPException +from fastapi.responses import StreamingResponse +from io import BytesIO from services.file_service import upload_file, download_file, delete_file router = APIRouter() @router.post("/upload") -def upload_file_route(file: UploadFile = File(...)): +async def upload_file_route(file: UploadFile = File(...)): + """Route to upload a file to cloud storage.""" return upload_file(file) -@router.get("/download/{file_name}") -def download_file_route(file_name: str): - return download_file(file_name) + +@router.get("/download") +async def download_route(file_url: str): + """Route to download a file from cloud storage using its URL.""" + try: + result = download_file(file_url) + file_content = result["file"] + media_type = result["media_type"] + filename = file_url.split('/')[-1] + + return StreamingResponse(BytesIO(file_content), media_type=media_type, headers={ + "Content-Disposition": f"attachment; filename={filename}" + }) + except HTTPException as e: + raise e + except Exception: + raise HTTPException(status_code=500, detail="An error occurred while downloading the file") + @router.delete("/delete/{file_name}") -def delete_file_route(file_name: str): - return delete_file(file_name) \ No newline at end of file +async def delete_file_route(file_name: str): + """Route to delete a file in cloud storage by its name.""" + return delete_file(file_name) diff --git a/backend/app/api/v1/tasks.py b/backend/app/api/v1/tasks.py index 4cc383e..82ec5d2 100644 --- a/backend/app/api/v1/tasks.py +++ b/backend/app/api/v1/tasks.py @@ -1,7 +1,10 @@ from celery import Celery +from firebase_admin import storage from core.config import settings +from core import firebase_init from core.ceaser_cipher import caesar_cipher, ReaderFactory -from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Literal import os @@ -11,34 +14,39 @@ backend=settings.CELERY_RESULT_BACKEND ) +bucket = storage.bucket(settings.GCS_BUCKET_NAME) -@celery.task -def encrypt_file(file_path: Path, shift: int): - reader = ReaderFactory.get_reader(file_path) - data = reader.read() - encrypted_data = caesar_cipher(data, shift, decrypt=False) - file_name, file_extension = os.path.splitext(file_path) - encrypted_file_path = f"{file_name}_encrypted{file_extension}" +def process_file(file_path: str, shift: int, operation: Literal["encrypt", "decrypt"]) -> str: + """Process file with Caesar cipher and upload to cloud storage.""" - reader.write(encrypted_data, output_file=encrypted_file_path) + with TemporaryDirectory() as temp_dir: + local_file_path = os.path.join(temp_dir, os.path.basename(file_path)) + blob = bucket.blob(file_path) + blob.download_to_filename(local_file_path) - os.remove(file_path) - - return encrypted_file_path + reader = ReaderFactory.get_reader(local_file_path) + data = reader.read() + processed_data = caesar_cipher(data, shift, decrypt=(operation == "decrypt")) + output_file_name = f"{os.path.splitext(file_path)[0]}_{operation}{os.path.splitext(file_path)[1]}" + processed_file_path = os.path.join(temp_dir, output_file_name) + reader.write(processed_data, output_file=processed_file_path) -@celery.task -def decrypt_file(file_path: str, shift: int): - reader = ReaderFactory.get_reader(file_path) - data = reader.read() - decrypted_data = caesar_cipher(data, shift, decrypt=True) + destination_path = f"{operation}/{os.path.basename(processed_file_path)}" + processed_blob = bucket.blob(destination_path) + processed_blob.upload_from_filename(processed_file_path) - file_name, file_extension = os.path.splitext(file_path) - decrypted_file_path = f"{file_name}_decrypted{file_extension}" + return processed_blob.public_url - reader.write(decrypted_data, output_file=decrypted_file_path) - os.remove(file_path) +@celery.task +def encrypt_file(file_path: str, shift: int) -> str: + """Encrypt a file and upload the result to cloud storage.""" + return process_file(file_path, shift, operation="encrypt") - return decrypted_file_path \ No newline at end of file + +@celery.task +def decrypt_file(file_path: str, shift: int) -> str: + """Decrypt a file and upload the result to cloud storage.""" + return process_file(file_path, shift, operation="decrypt") diff --git a/backend/app/core/ceaser_cipher.py b/backend/app/core/ceaser_cipher.py index 4dc093d..7397f44 100644 --- a/backend/app/core/ceaser_cipher.py +++ b/backend/app/core/ceaser_cipher.py @@ -3,8 +3,8 @@ from pypdf import PdfReader from reportlab.platypus import SimpleDocTemplate, PageBreak, Paragraph from reportlab.lib.styles import getSampleStyleSheet -from reportlab.lib.units import inch from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import inch from abc import ABC, abstractmethod @@ -22,36 +22,14 @@ def write(self, data, output_file=None): pass -class ReaderFactory: - """Factory class to get the appropriate reader based on file type""" - readers = { - ".pdf": lambda file_path: CustomPdfReader(file_path), - ".txt": lambda file_path: CustomTextReader(file_path), - ".csv": lambda file_path: CustomCsvReader(file_path), - } - - @staticmethod - def get_reader(file_path: Path): - file_path = Path(file_path) - reader_class = ReaderFactory.readers.get(file_path.suffix) - if reader_class: - return reader_class(file_path) - raise ValueError("Unsupported file type") - - class CustomPdfReader(BaseReader): """Custom PDF reader class""" def read(self): - text = "" with open(self.file_path, "rb") as f: reader = PdfReader(f) - for page in reader.pages: - text += page.extract_text() - return text - + return "".join(page.extract_text() for page in reader.pages) def write(self, data, output_file=None): - output_file = output_file doc = SimpleDocTemplate( output_file, pagesize=A4, @@ -60,13 +38,8 @@ def write(self, data, output_file=None): rightMargin=0.8 * inch, leftMargin=0.8 * inch, ) - story = [] styles = getSampleStyleSheet() - preformatted_style = styles["Normal"] - - preformatted = Paragraph(data, preformatted_style) - story.append(preformatted) - story.append(PageBreak()) + story = [Paragraph(data if isinstance(data, str) else "\n".join(data), styles["Normal"]), PageBreak()] doc.build(story) @@ -77,7 +50,6 @@ def read(self): return f.read() def write(self, data, output_file=None): - output_file = output_file with open(output_file, "w", encoding="utf-8") as f: f.write(data) @@ -86,43 +58,41 @@ class CustomCsvReader(BaseReader): """Custom CSV reader class""" def read(self): with open(self.file_path, "r", encoding="utf-8") as f: - reader = csv.reader(f) - return [row for row in reader] + return list(csv.reader(f)) def write(self, data, output_file=None): - output_file = output_file with open(output_file, "w", newline="", encoding="utf-8") as f: - writer = csv.writer(f) - writer.writerows(data) + csv.writer(f).writerows(data) + + +class ReaderFactory: + """Factory class to get the appropriate reader based on file type""" + readers = { + ".pdf": CustomPdfReader, + ".txt": CustomTextReader, + ".csv": CustomCsvReader, + } + + @staticmethod + def get_reader(file_path: Path): + file_path = Path(file_path) + reader_class = ReaderFactory.readers.get(file_path.suffix) + if reader_class: + return reader_class(file_path) + raise ValueError("Unsupported file type") def cryptify_string(text, shift): """Encrypt/Decrypt text using Caesar cipher""" - result = [] - for char in text: - if char.isalpha(): - offset = 65 if char.isupper() else 97 - result.append(chr((ord(char) - offset + shift) % 26 + offset)) - else: - result.append(char) - return "".join(result) + return "".join( + chr((ord(char) - (65 if char.isupper() else 97) + shift) % 26 + (65 if char.isupper() else 97)) + if char.isalpha() else char for char in text + ) def caesar_cipher(data, shift, decrypt=False): """Caesar cipher implementation""" - if decrypt: - shift = -shift - - result = [] + shift = -shift if decrypt else shift if isinstance(data, list): - for row in data: - cryptify_row = [] - for item in row: - if isinstance(item, str): - cryptify_row.append(cryptify_string(item, shift)) - else: - cryptify_row.append(item) - result.append(cryptify_row) - else: - result.append(cryptify_string(data, shift)) - return result + return [[cryptify_string(str(item), shift) if isinstance(item, str) else item for item in row] for row in data] + return cryptify_string(data, shift) diff --git a/backend/app/crud/document_crud.py b/backend/app/crud/document_crud.py index a1b2969..275c376 100644 --- a/backend/app/crud/document_crud.py +++ b/backend/app/crud/document_crud.py @@ -4,7 +4,7 @@ def create_document(db: Session, document: document_schema.DocumentCreate): db_document = document_model.Document( - title=document.title, + task_id=document.task_id, original_document_url=document.original_document_url, processed_document_url=document.processed_document_url, user_id=document.user_id, diff --git a/backend/app/main.py b/backend/app/main.py index 9cd707e..ede0187 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -17,4 +17,5 @@ @app.get("/") def read_root(): + """Root route""" return {"message": "Welcome to Cryptify API!"} \ No newline at end of file diff --git a/backend/app/models/document_model.py b/backend/app/models/document_model.py index 40929b1..4cf5173 100644 --- a/backend/app/models/document_model.py +++ b/backend/app/models/document_model.py @@ -10,9 +10,9 @@ class Document(Base): id = Column(Integer, primary_key= True, nullable=False) user_id = Column(Integer, ForeignKey("users.id")) - title = Column(String, nullable=False) + task_id = Column(String, nullable=True) original_document_url = Column(String, nullable=False) - processed_document_url = Column(String, nullable=False) + processed_document_url = Column(String, nullable=True) date_created = Column(DateTime, default=datetime.now(timezone.utc)) date_updated = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc)) diff --git a/backend/app/schemas/document_schema.py b/backend/app/schemas/document_schema.py index 84827d5..67e0c70 100644 --- a/backend/app/schemas/document_schema.py +++ b/backend/app/schemas/document_schema.py @@ -4,7 +4,7 @@ class DocumentBase(BaseModel): """Document Model""" - title : str + task_id : str original_document_url : str processed_document_url : str date_created : Optional[datetime] diff --git a/backend/app/services/file_service.py b/backend/app/services/file_service.py index c734cce..a3b0626 100644 --- a/backend/app/services/file_service.py +++ b/backend/app/services/file_service.py @@ -1,27 +1,63 @@ -from fastapi import File, UploadFile, HTTPException +from fastapi import HTTPException, UploadFile from firebase_admin import storage -from core import firebase_init +from urllib.parse import urlparse from core.config import settings +from uuid import uuid4 bucket = storage.bucket(settings.GCS_BUCKET_NAME) -def upload_file(file: UploadFile = File(...)): - blob = bucket.blob(file.filename) +def get_media_type(file_path: str) -> str: + """Determine media type based on file extension.""" + media_types = { + ".pdf": "application/pdf", + ".txt": "text/plain", + ".csv": "text/csv", + } + return media_types.get(file_path[file_path.rfind('.'):], "application/octet-stream") + + +def upload_file(file: UploadFile): + """Upload a file to cloud storage, adjusting for spaces in file name.""" + sanitized_filename = file.filename.replace(" ", "_") + + unique_suffix = uuid4().hex[:8] + sanitized_filename = f"{sanitized_filename.rsplit('.', 1)[0]}_{unique_suffix}.{sanitized_filename.rsplit('.', 1)[-1]}" + + blob = bucket.blob(sanitized_filename) blob.upload_from_file(file.file) - return {"message": "File uploaded successfully", "file_path": blob.public_url, "file_name": blob.name} + return { + "message": "File uploaded successfully", + "file_path": blob.public_url, + "file_name": blob.name + } -def download_file(file_name: str): - blob = bucket.blob(file_name) + +def download_file(file_url: str): + """Download a file from cloud storage based on its URL.""" + parsed_url = urlparse(file_url) + file_path = parsed_url.path.lstrip("/") + + bucket_name = bucket.name + if file_path.startswith(f"{bucket_name}/"): + file_path = file_path[len(bucket_name) + 1:] + + blob = bucket.blob(file_path) if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") - return blob.public_url + + media_type = get_media_type(file_path) + return { + "file": blob.download_as_string(), + "media_type": media_type + } def delete_file(file_name: str): + """Delete a file in cloud storage by its name.""" blob = bucket.blob(file_name) if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") blob.delete() - return {"message": "File deleted successfully"} \ No newline at end of file + return {"message": "File deleted successfully"} diff --git a/backend/app/services/process_service.py b/backend/app/services/process_service.py index 6127b99..3a0c967 100644 --- a/backend/app/services/process_service.py +++ b/backend/app/services/process_service.py @@ -3,75 +3,93 @@ from celery.result import AsyncResult from firebase_admin import storage from schemas.document_schema import DocumentCreate +from models.document_model import Document from crud.document_crud import create_document from core.security import get_user_id_from_token from core.config import settings -from core import firebase_init from api.v1.tasks import encrypt_file, decrypt_file from datetime import datetime, timezone +from typing import Optional, Literal, Dict bucket = storage.bucket(settings.GCS_BUCKET_NAME) -def encrypt_uploaded_file( - file_path: str, - shift: int, - token: str, - db: Session -): +def get_current_utc_time() -> datetime: + """Helper function to get the current UTC time.""" + return datetime.now(timezone.utc) + + +def initiate_file_task( + file_path: str, + shift: int, + token: Optional[str], + db: Session, + operation: Literal["encrypt", "decrypt"] +) -> Dict[str, str]: + + """Initiate file encryption or decryption task, optionally creating a document record if authenticated.""" user_id = get_user_id_from_token(token) if token else None blob = bucket.blob(file_path) + if not blob.exists(): raise HTTPException(status_code=404, detail="File not found") - task = encrypt_file.delay(file_path, shift) + task = encrypt_file.delay(file_path, shift) if operation == "encrypt" else decrypt_file.delay(file_path, shift) + if token and user_id: document = DocumentCreate( - title=blob.name, + task_id=task.id, original_document_url=file_path, - processed_document_url=blob.public_url, + processed_document_url="", user_id=user_id, - date_created=datetime.now(timezone.utc), - date_updated=datetime.now(timezone.utc), - ) + date_created=get_current_utc_time(), + date_updated=get_current_utc_time(), + ) create_document(db, document) - return {"message": "File encryption started", "task_id": task.id} + return {"message": f"File {operation}ion started", "task_id": task.id} + + +def encrypt_uploaded_file( + file_path: str, + shift: int, + token: Optional[str], + db: Session + ) -> Dict[str, str]: + + """Handle file encryption initiation, creating a document record if authenticated.""" + return initiate_file_task(file_path, shift, token, db, operation="encrypt") def decrypt_uploaded_file( - file_path: str, - shift: int, - token: str, - db: Session -): - user_id = get_user_id_from_token(token) if token else None - blob = bucket.blob(file_path) - if not blob.exists(): - raise HTTPException(status_code=404, detail="File not found") - - task = decrypt_file.delay(file_path, shift) - if token and user_id: - document = DocumentCreate( - title=blob.name, - original_document_url=file_path, - processed_document_url=blob.public_url, - user_id=user_id, - date_created=datetime.now(timezone.utc), - date_updated=datetime.now(timezone.utc), - ) - create_document(db, document) - - return {"message": "File decryption started", "task_id": task.id} - + file_path: str, + shift: int, + token: Optional[str], + db: Session + ) -> Dict[str, str]: + + """Handle file decryption initiation, creating a document record if authenticated.""" + return initiate_file_task(file_path, shift, token, db, operation="decrypt") + -def get_task_status(task_id: str): +def update_document_with_result(task_id: str, file_path: str, db: Session) -> None: + """Update the document record with the processed file path upon task completion, if authenticated.""" + document = db.query(Document).filter(Document.task_id == task_id).first() + if document: + document.processed_document_url = file_path + document.date_updated = get_current_utc_time() + db.commit() + + +def get_task_status(task_id: str, db: Session) -> Dict[str, str]: + """Retrieve task status and update document record if task is successful.""" task = AsyncResult(task_id) + if task.state == "PENDING": return {"status": "pending", "details": "Task is being processed"} elif task.state == "SUCCESS": file_path = task.result - return {"status": "completed", "details": file_path} + update_document_with_result(task_id, file_path, db) + return {"status": "success", "details": "completed", "file_path": file_path} else: - return {"status": "failed", "details": "Task failed"} - \ No newline at end of file + return {"status": "failed", "details": str(task.info)}