Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a6529c9
Update utils.py
itsmeakhil Jul 1, 2025
dbf1e2f
Merge branch 'preview' of https://github.com/zecrypt-io/zecrypt-serve…
itsmeakhil Jul 1, 2025
01bb4f4
Merge pull request #79 from zecrypt-io/feature/ananducv/extension
anandude Jul 10, 2025
09a17bf
Update jwt_utils.py
itsmeakhil Jul 10, 2025
a6a1dca
Merge pull request #81 from zecrypt-io/main
itsmeakhil Jul 15, 2025
c3522cf
French
itsmeakhil Sep 24, 2025
e32c2fd
Merge pull request #83 from zecrypt-io/akhil/frontend/add-translations
itsmeakhil Sep 24, 2025
5b50b30
Spanish
itsmeakhil Sep 24, 2025
629fc60
Afrikans
itsmeakhil Sep 24, 2025
fefc925
Added arabic
itsmeakhil Sep 24, 2025
45248c6
Merge pull request #84 from zecrypt-io/akhil/frontend/add-translations
itsmeakhil Sep 24, 2025
e2d110d
drive apis
itsmeakhil Oct 3, 2025
74098f8
Merge pull request #85 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 3, 2025
10b827e
Update api.py
itsmeakhil Oct 3, 2025
487853b
folders
itsmeakhil Oct 3, 2025
28978f6
Merge pull request #86 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 3, 2025
3acd743
Update api.py
itsmeakhil Oct 3, 2025
ce1c7b1
Merge pull request #87 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 3, 2025
097f45e
basic ui and api integration done
anandude Oct 6, 2025
7bc2894
depth issue while creating folder fixed
anandude Oct 7, 2025
87d172b
api url construction issue fixed
anandude Oct 7, 2025
6974bcb
Update schema.py
itsmeakhil Oct 7, 2025
8a48034
Update schema.py
itsmeakhil Oct 7, 2025
142e590
Merge pull request #88 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 7, 2025
39a8ca8
api payload uploaded to not include parent id in root folder
anandude Oct 7, 2025
e06b345
files
itsmeakhil Oct 7, 2025
620a4a2
Merge pull request #89 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 7, 2025
64d7e8c
files
itsmeakhil Oct 7, 2025
acda20d
Merge pull request #90 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 7, 2025
0058080
folders list
itsmeakhil Oct 9, 2025
eb2f69b
Merge pull request #91 from zecrypt-io/akhil/drive/apis
itsmeakhil Oct 9, 2025
67f124b
Update requirements.txt
itsmeakhil Oct 9, 2025
52961f1
Merge branch 'preview' of https://github.com/zecrypt-io/zecrypt-serve…
itsmeakhil Oct 9, 2025
da321b3
all folder apis integrated
anandude Oct 9, 2025
e22f023
file upload apis done (remaining preview and download)
anandude Oct 9, 2025
5705c8c
files
itsmeakhil Oct 10, 2025
66a8e51
translations added for drive
anandude Oct 10, 2025
adc4d1c
files in root not visible issue fix
anandude Oct 10, 2025
39d7977
Update s3_utils.py
itsmeakhil Oct 11, 2025
8a3bd5a
file upload to digitalocean working (duplicate issue fix needed)
anandude Oct 13, 2025
612e2d8
duplicate file issue fixed
anandude Oct 13, 2025
0ce4196
Update schema.py
itsmeakhil Oct 14, 2025
ce924b6
Update services.py
itsmeakhil Oct 14, 2025
7d225be
file decryption and download working
anandude Oct 14, 2025
f9884d5
files preview feature implemented
anandude Oct 14, 2025
717c70c
Merge branch 'preview' into feature/ananducv/drive-1
anandude Oct 14, 2025
2dc28b9
Merge pull request #92 from zecrypt-io/feature/ananducv/drive-1
itsmeakhil Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/backend-server/app/api/v1/web/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from app.api.v1.web.user import api as user_router
from app.api.v1.web.secrets import api as secrets_router
from app.api.v1.web.dashboard import api as dashboard_router

from app.api.v1.web.drive import api as drive_router
api_router = APIRouter()

api_router.prefix = "/web"
Expand All @@ -18,3 +18,5 @@
api_router.include_router(user_router.router, tags=["User"])
api_router.include_router(workspace_router.router, tags=["Workspace"])
api_router.include_router(dashboard_router.router, tags=["Dashboard"])
api_router.include_router(drive_router.router)

11 changes: 11 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import APIRouter
from app.api.v1.web.drive.folders import api as folders_router
from app.api.v1.web.drive.files import api as files_router


router = APIRouter()

router.prefix = "/drive"

router.include_router(folders_router.router, tags=["Drive: Folders"])
router.include_router(files_router.router, tags=["Drive: Files"])
Empty file.
27 changes: 27 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/files/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from fastapi import APIRouter,Request,Depends,UploadFile
from app.api.v1.web.drive.files.schema import RenameFile, MoveFile, DeleteFiles, GetPresignedUrl
from app.api.v1.web.auth.schema import UserDetails
from app.framework.permission_services.service import get_current_user
from app.api.v1.web.drive.files.services import rename_file, get_presigned_url, delete_files, move_files

router = APIRouter()

FILE_URL ="/file"

@router.post(FILE_URL+"/get-presigned-url")
async def get_presigned_url_api(payload: GetPresignedUrl, user: UserDetails = Depends(get_current_user)):
return await get_presigned_url(user, payload.model_dump())

@router.post(FILE_URL+"/rename")
async def rename_file_api(payload: RenameFile, user: UserDetails = Depends(get_current_user)):
return await rename_file(user, payload.model_dump())


@router.delete(FILE_URL+"/delete")
async def delete_files_api(payload: DeleteFiles, user: UserDetails = Depends(get_current_user)):
return await delete_files(user, payload.model_dump())


@router.post(FILE_URL+"/move")
async def move_files_api(payload: MoveFile, user: UserDetails = Depends(get_current_user)):
return await move_files(user, payload.model_dump())
24 changes: 24 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/files/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime


class RenameFile(BaseModel):
name: str = Field(..., description="File name")
file_id: str = Field(..., description="File ID")
parent_id: Optional[str] = Field(None, description="Parent folder ID, None for root")

class MoveFile(BaseModel):
file_ids: List[str] = Field(..., description="File IDs")
parent_id: str = Field(..., description="Parent folder ID")

class DeleteFiles(BaseModel):
file_ids: List[str] = Field(..., description="File IDs")

class GetPresignedUrl(BaseModel):
file_type: str = Field(..., description="File type")
name: str = Field(..., description="File name")
size: int = Field(..., description="File size")
file_path: str = Field(..., description="File path")
parent_id: Optional[str] = Field(None, description="Parent folder ID")
iv: str = Field(..., description="IV")
130 changes: 130 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/files/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from app.framework.mongo_db import base_manager as db_manager
from app.managers.collection_names import FILES, FOLDERS
from app.utils.utils import response_helper, create_uuid, get_file_extension, get_folders_from_path, create_timestamp
from app.utils.i8ns import translate
from app.utils.s3_utils import generate_upload_url, generate_download_url



def get_files_list(user, parent_id=None):
db = user.get("db")
query = {"created_by": user.get("user_id")}
if parent_id:
query["parent_id"] = parent_id
files = db_manager.find(db, FILES, query)
for file in files:
file["file_url"]= generate_download_url(file.get("key"), 360000)
return files

async def get_presigned_url(user, payload):
db = user.get("db")
user_id = user.get("user_id")
file_name = payload.get("name")

# Check if file already exists for this user
query = {
"lower_name": file_name.strip().lower(),
"created_by": user_id
}
if payload.get("parent_id"):
query["parent_id"] = payload.get("parent_id")

file = db_manager.find_one(db, FILES, query)
if file:
return response_helper(400, translate("drive.files.already_exists"))

# Generate file paths
file_id = create_uuid()
file_extension = get_file_extension(file_name)
final_file_path = f"{user_id}/files/{file_id}.{file_extension}"

# Get folders from path
folders = get_folders_from_path(payload.get("file_path"))
parent_id = payload.get("parent_id")

# Create folder hierarchy if folders exist
for folder_name in folders:
query = {
"created_by": user_id,
"lower_name": folder_name.strip().lower()
}
if parent_id:
query["parent_id"] = parent_id

folder_details = db_manager.find_one(db, FOLDERS, query)
if not folder_details:
# Create new folder
folder_data = {
"doc_id": create_uuid(),
"name": folder_name.strip(),
"parent_id": parent_id,
"created_by": user_id,
"lower_name": folder_name.strip().lower(),
}
db_manager.insert_one(db, FOLDERS, folder_data)
parent_id = folder_data.get("doc_id")
else:
# Use existing folder
parent_id = folder_details.get("doc_id")

# Insert file record (always, regardless of whether folders exist)
file_data = {
"doc_id": file_id,
"type": payload.get("file_type"),
"name": file_name,
"lower_name": file_name.strip().lower(),
"size": payload.get("size"),
"path": payload.get("file_path"),
"key": final_file_path,
"parent_id": parent_id,
"created_by": user_id,
"iv": payload.get("iv"),
}
db_manager.insert_one(db, FILES, file_data)

# Generate presigned upload URL
upload_url = generate_upload_url(final_file_path)

return response_helper(200, translate("file.get_presigned_url"), data=upload_url)


async def rename_file(user, payload):
db = user.get("db")
name = payload.get("name").strip()
file_id = payload.get("file_id")
lower_name = name.lower()
query = {"created_by": user.get("user_id"), "lower_name": lower_name, "doc_id": {"$ne": file_id}}

if payload.get("parent_id"):
query["parent_id"] = payload.get("parent_id")

file = db_manager.find_one(db, FILES, query)
if file:
return response_helper(400, translate("drive.files.already_exists"))

db_manager.update_one(db, FILES, {"doc_id": file_id, "created_by": user.get("user_id")}, {"$set": {"name": name, "lower_name": lower_name}})
return response_helper(200, translate("drive.files.renamed"))


async def delete_files(user, payload):
db = user.get("db")
file_ids = payload.get("file_ids")
user_id = user.get("user_id")
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()}})
return response_helper(200, translate("drive.files.deleted"))


async def move_files(user, payload):
db = user.get("db")
file_ids = payload.get("file_ids")
parent_id = payload.get("parent_id")
query = {"created_by": user.get("user_id"), "doc_id": {"$in": file_ids}}
files = db_manager.find(db, FILES, query)
files_moved = []
for item in files:
individual_query = {"created_by": user.get("user_id"), "parent_id": parent_id, "lower_name": item.get("lower_name")}
if not db_manager.find_one(db, FILES, individual_query):
db_manager.update_one(db, FILES, {"doc_id": item.get("doc_id")}, {"$set": {"parent_id": parent_id}})
files_moved.append(item.get("name"))

return response_helper(200, translate("drive.files.moved"), files_moved=files_moved)
32 changes: 32 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/folders/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from fastapi import APIRouter,Depends,Query
from app.api.v1.web.drive.folders.schema import CreateFolder, RenameFolder, MoveFolder, DeleteFolders
from app.api.v1.web.auth.schema import UserDetails
from app.framework.permission_services.service import get_current_user
from app.api.v1.web.drive.folders.services import create_folder,delete_folders,rename_folder, move_folders, get_folders_list

FOLDER_URL ="/folder"
router = APIRouter()

@router.post(FOLDER_URL+"/create")
async def create_folder_api(payload: CreateFolder, user: UserDetails = Depends(get_current_user)):
return await create_folder(user, payload.model_dump())


@router.delete(FOLDER_URL+"/delete")
async def delete_folder_api(payload: DeleteFolders, user: UserDetails = Depends(get_current_user)):
return await delete_folders(user, payload.model_dump())


@router.post(FOLDER_URL+"/rename")
async def rename_folder_api(payload: RenameFolder, user: UserDetails = Depends(get_current_user)):
return await rename_folder(user, payload.model_dump())


@router.post(FOLDER_URL+"/move")
async def move_folder_api(payload: MoveFolder, user: UserDetails = Depends(get_current_user)):
return await move_folders(user, payload.model_dump())


@router.get(FOLDER_URL+"/list")
async def get_all_folders_api(parent_id: str = Query(None, description="Parent folder ID", ge=1), user: UserDetails = Depends(get_current_user)):
return await get_folders_list(user, parent_id)
19 changes: 19 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/folders/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime

class CreateFolder(BaseModel):
name: str
parent_id: Optional[str]= None

class RenameFolder(BaseModel):
name: str
folder_id: str
parent_id: Optional[str] = None

class MoveFolder(BaseModel):
folder_ids: List[str]
parent_id: str

class DeleteFolders(BaseModel):
folder_ids: List[str]
102 changes: 102 additions & 0 deletions packages/backend-server/app/api/v1/web/drive/folders/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

from app.framework.mongo_db import base_manager as db_manager
from app.managers.collection_names import FILES, FOLDERS
from app.utils.utils import response_helper, create_uuid, create_timestamp
from app.utils.i8ns import translate
from app.api.v1.web.drive.files.services import get_files_list

async def create_folder(user, payload):
db = user.get("db")
folder_id = create_uuid()
query={
"created_by": user.get("user_id"),
"lower_name": payload.get("name").strip().lower(),
}
if payload.get("parent_id"):
query["parent_id"] = payload.get("parent_id")
folder = db_manager.find_one(db, FOLDERS, query)
if folder:
return response_helper(400, translate("drive.folders.already_exists"))

folder = {
"doc_id": folder_id,
"name": payload.get("name"),
"parent_id": payload.get("parent_id"),
"created_by": user.get("user_id"),
"lower_name": payload.get("name").strip().lower(),
"created_at": create_timestamp(),

}
db_manager.insert_one(db, FOLDERS, folder)
return response_helper(200, translate("drive.folders.created"))


async def delete_folders(user, payload):
db = user.get("db")
folder_ids = payload.get("folder_ids")
user_id = user.get("user_id")
db_manager.update_many(db, FOLDERS, {"doc_id": {"$in":folder_ids}, "created_by": user_id}, {"$set": {"access": False,"delete_by":user_id, "deleted_at": create_timestamp()}})
return response_helper(200, translate("drive.folders.deleted"))


async def rename_folder(user, payload):
db = user.get("db")
name = payload.get("name").strip()
folder_id = payload.get("folder_id")
lower_name= name.lower()
query={"created_by": user.get("user_id"), "lower_name": lower_name}

if payload.get("parent_id"):
query["parent_id"] = payload.get("parent_id")
folder = db_manager.find_one(db, FOLDERS, query)

if folder:
return response_helper(400, translate("drive.folders.already_exists"))

db_manager.update_one(db, FOLDERS, {"doc_id": folder_id, "created_by": user.get("user_id")}, {"$set": {"name": name, "lower_name": lower_name}})
return response_helper(200, translate("drive.folders.renamed"))


async def move_folders(user, payload):
db = user.get("db")
folder_ids = payload.get("folder_ids")
parent_id = payload.get("parent_id")
query={"created_by": user.get("user_id"), "doc_id": {"$in":folder_ids}}
folders = db_manager.find(db, FOLDERS, query)
folders_moved=[]
for item in folders:
individual_query={"created_by": user.get("user_id"), "parent_id": parent_id, "lower_name": item.get("lower_name")}
if not db_manager.find_one(db, FOLDERS, individual_query):
db_manager.update_one(db, FOLDERS, {"doc_id": item.get("doc_id")}, {"$set": {"parent_id": parent_id}})
folders_moved.append(item.get("name"))


db_manager.update_many(db, FOLDERS, {"doc_id": {"$in":folder_ids}, "created_by": user.get("user_id")}, {"$set": {"parent_id": parent_id}})
return response_helper(200, translate("drive.folders.moved"), folders_moved=folders_moved)

async def get_folders_list(user, parent_id):
query = {"created_by": user.get("user_id")}
if parent_id:
query["parent_id"] = parent_id

folders = get_folders(user, parent_id)

for folder in folders:
folder["files"] = get_files_list(user, folder.get("doc_id"))
folder["sub_folders"] = get_folders(user, folder.get("doc_id"))

data= {
"folders": folders,
"files": get_files_list(user, parent_id)
}
return response_helper(200, translate("drive.folders.list"), data=data)



def get_folders(user, parent_id=None):
db = user.get("db")
query = {"created_by": user.get("user_id")}
if parent_id:
query["parent_id"] = parent_id
folders = db_manager.find(db, FOLDERS, query)
return folders
5 changes: 5 additions & 0 deletions packages/backend-server/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ class Settings(BaseSettings):

VALKEY_URL: str

DO_SPACES_KEY:str
DO_SPACES_SECRET:str
DO_SPACES_REGION:str
DO_SPACES_BUCKET:str
DO_SPACES_ENDPOINT:str
class Config:
case_sensitive = True
env_file = ".env"
Expand Down
Loading
Loading