Skip to content

Commit 64d7e8c

Browse files
committed
files
1 parent e06b345 commit 64d7e8c

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

packages/backend-server/app/api/v1/web/drive/files/__init__.py

Whitespace-only changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from fastapi import APIRouter,Request,Depends,UploadFile
2+
from app.api.v1.web.drive.files.schema import RenameFile, MoveFile, DeleteFiles, GetPresignedUrl
3+
from app.api.v1.web.auth.schema import UserDetails
4+
from app.framework.permission_services.service import get_current_user
5+
from app.api.v1.web.drive.files.services import rename_file, get_presigned_url, delete_files, move_files
6+
7+
router = APIRouter()
8+
9+
FILE_URL ="/file"
10+
11+
@router.post(FILE_URL+"/get-presigned-url")
12+
async def get_presigned_url_api(payload: GetPresignedUrl, user: UserDetails = Depends(get_current_user)):
13+
return await get_presigned_url(user, payload.model_dump())
14+
15+
@router.post(FILE_URL+"/rename")
16+
async def rename_file_api(payload: RenameFile, user: UserDetails = Depends(get_current_user)):
17+
return await rename_file(user, payload.model_dump())
18+
19+
20+
@router.delete(FILE_URL+"/delete")
21+
async def delete_files_api(payload: DeleteFiles, user: UserDetails = Depends(get_current_user)):
22+
return await delete_files(user, payload.model_dump())
23+
24+
25+
@router.post(FILE_URL+"/move")
26+
async def move_files_api(payload: MoveFile, user: UserDetails = Depends(get_current_user)):
27+
return await move_files(user, payload.model_dump())
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from pydantic import BaseModel, Field
2+
from typing import Optional, List
3+
from datetime import datetime
4+
5+
6+
class RenameFile(BaseModel):
7+
name: str = Field(..., description="File name")
8+
file_id: str = Field(..., description="File ID")
9+
parent_id: Optional[str] = Field(None, description="Parent folder ID, None for root")
10+
11+
class MoveFile(BaseModel):
12+
file_ids: List[str] = Field(..., description="File IDs")
13+
parent_id: str = Field(..., description="Parent folder ID")
14+
15+
class DeleteFiles(BaseModel):
16+
file_ids: List[str] = Field(..., description="File IDs")
17+
18+
class GetPresignedUrl(BaseModel):
19+
file_type: str = Field(..., description="File type")
20+
name: str = Field(..., description="File name")
21+
size: int = Field(..., description="File size")
22+
file_path: str = Field(..., description="File path")
23+
parent_id: Optional[str] = Field(None, description="Parent folder ID")
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from app.framework.mongo_db import base_manager as db_manager
2+
from app.managers.collection_names import FILES, FOLDERS
3+
from app.utils.utils import response_helper, create_uuid, get_file_extension, get_folders_from_path, create_timestamp
4+
from app.utils.i8ns import translate
5+
from app.utils.s3_utils import generate_upload_url
6+
7+
async def get_presigned_url(user, payload):
8+
db = user.get("db")
9+
user_id = user.get("user_id")
10+
file_name = payload.get("name")
11+
12+
# Check if file already exists for this user
13+
query = {
14+
"lower_name": file_name.strip().lower(),
15+
"created_by": user_id
16+
}
17+
if payload.get("parent_id"):
18+
query["parent_id"] = payload.get("parent_id")
19+
20+
file = db_manager.find_one(db, FILES, query)
21+
if file:
22+
return response_helper(400, translate("drive.files.already_exists"))
23+
24+
# Generate file paths
25+
file_id = create_uuid()
26+
file_extension = get_file_extension(file_name)
27+
final_file_path = f"{user_id}/files/{file_id}.{file_extension}"
28+
29+
# Get folders from path
30+
folders = get_folders_from_path(payload.get("file_path"))
31+
parent_id = payload.get("parent_id")
32+
33+
# Create folder hierarchy if folders exist
34+
for folder_name in folders:
35+
query = {
36+
"created_by": user_id,
37+
"lower_name": folder_name.strip().lower()
38+
}
39+
if parent_id:
40+
query["parent_id"] = parent_id
41+
42+
folder_details = db_manager.find_one(db, FOLDERS, query)
43+
if not folder_details:
44+
# Create new folder
45+
folder_data = {
46+
"doc_id": create_uuid(),
47+
"name": folder_name.strip(),
48+
"parent_id": parent_id,
49+
"created_by": user_id,
50+
"lower_name": folder_name.strip().lower(),
51+
}
52+
db_manager.insert_one(db, FOLDERS, folder_data)
53+
parent_id = folder_data.get("doc_id")
54+
else:
55+
# Use existing folder
56+
parent_id = folder_details.get("doc_id")
57+
58+
# Insert file record (always, regardless of whether folders exist)
59+
file_data = {
60+
"doc_id": file_id,
61+
"type": payload.get("file_type"),
62+
"name": file_name,
63+
"lower_name": file_name.strip().lower(),
64+
"size": payload.get("size"),
65+
"path": payload.get("file_path"),
66+
"key": final_file_path,
67+
"parent_id": parent_id,
68+
"created_by": user_id,
69+
}
70+
db_manager.insert_one(db, FILES, file_data)
71+
72+
# Generate presigned upload URL
73+
upload_url = generate_upload_url(final_file_path)
74+
75+
return response_helper(200, translate("file.get_presigned_url"), data=upload_url)
76+
77+
78+
async def rename_file(user, payload):
79+
db = user.get("db")
80+
name = payload.get("name").strip()
81+
file_id = payload.get("file_id")
82+
lower_name = name.lower()
83+
query = {"created_by": user.get("user_id"), "lower_name": lower_name, "doc_id": {"$ne": file_id}}
84+
85+
if payload.get("parent_id"):
86+
query["parent_id"] = payload.get("parent_id")
87+
88+
file = db_manager.find_one(db, FILES, query)
89+
if file:
90+
return response_helper(400, translate("drive.files.already_exists"))
91+
92+
db_manager.update_one(db, FILES, {"doc_id": file_id, "created_by": user.get("user_id")}, {"$set": {"name": name, "lower_name": lower_name}})
93+
return response_helper(200, translate("drive.files.renamed"))
94+
95+
96+
async def delete_files(user, payload):
97+
db = user.get("db")
98+
file_ids = payload.get("file_ids")
99+
user_id = user.get("user_id")
100+
db_manager.update_many(db, FILES, {"doc_id": {"$in": file_ids}, "created_by": user_id}, {"$set": {"access": False, "delete_by": user_id, "deleted_at": create_timestamp()}})
101+
return response_helper(200, translate("drive.files.deleted"))
102+
103+
104+
async def move_files(user, payload):
105+
db = user.get("db")
106+
file_ids = payload.get("file_ids")
107+
parent_id = payload.get("parent_id")
108+
query = {"created_by": user.get("user_id"), "doc_id": {"$in": file_ids}}
109+
files = db_manager.find(db, FILES, query)
110+
files_moved = []
111+
for item in files:
112+
individual_query = {"created_by": user.get("user_id"), "parent_id": parent_id, "lower_name": item.get("lower_name")}
113+
if not db_manager.find_one(db, FILES, individual_query):
114+
db_manager.update_one(db, FILES, {"doc_id": item.get("doc_id")}, {"$set": {"parent_id": parent_id}})
115+
files_moved.append(item.get("name"))
116+
117+
return response_helper(200, translate("drive.files.moved"), files_moved=files_moved)

packages/backend-server/app/core/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ class Settings(BaseSettings):
1515

1616
VALKEY_URL: str
1717

18+
DO_SPACES_KEY:str
19+
DO_SPACES_SECRET:str
20+
DO_SPACES_REGION:str
21+
DO_SPACES_BUCKET:str
22+
DO_SPACES_ENDPOINT:str
1823
class Config:
1924
case_sensitive = True
2025
env_file = ".env"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import boto3
2+
from app.core.config import settings
3+
4+
5+
# DigitalOcean Spaces config
6+
session = boto3.session.Session()
7+
client = session.client(
8+
"s3",
9+
region_name=settings.DO_SPACES_REGION,
10+
endpoint_url=f"https://{settings.DO_SPACES_REGION}.digitaloceanspaces.com",
11+
aws_access_key_id=settings.DO_SPACES_KEY,
12+
aws_secret_access_key=settings.DO_SPACES_SECRET,
13+
)
14+
15+
BUCKET_NAME = settings.DO_SPACES_BUCKET
16+
17+
18+
def generate_upload_url(file_name,expires_in=3600):
19+
try:
20+
url = client.generate_presigned_url(
21+
"put_object",
22+
Params={"Bucket": BUCKET_NAME, "Key": file_name},
23+
ExpiresIn=expires_in,
24+
)
25+
return {"upload_url": url}
26+
except Exception as e:
27+
return {"error": str(e)}
28+
29+
def generate_download_url(file_name,expires_in=3600):
30+
try:
31+
url = client.generate_presigned_url(
32+
"get_object",
33+
Params={"Bucket": BUCKET_NAME, "Key": file_name},
34+
ExpiresIn=expires_in,
35+
)
36+
return {"download_url": url}
37+
except Exception as e:
38+
return {"error": str(e)}

packages/backend-server/app/utils/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import json
77
from datetime import datetime
88
import pytz
9+
import os
910

11+
from pathlib import Path
1012

1113

1214
def response_helper(
@@ -63,3 +65,12 @@ def generate_query_hash(query, projection=None):
6365

6466
def create_timestamp():
6567
return datetime.now(pytz.utc)
68+
69+
def get_file_extension(filename):
70+
_, ext = os.path.splitext(filename)
71+
return ext.lstrip(".")
72+
73+
74+
75+
def get_folders_from_path(path: str):
76+
return [part for part in Path(path).parent.parts]

0 commit comments

Comments
 (0)