chore: add token revocation, caching, concurrency, and WebDAV lock modules

This commit is contained in:
2025-11-29 10:17:47 +00:00
parent acf70b7019
commit 525784aa6f
40 changed files with 4855 additions and 566 deletions
+97 -39
View File
@@ -22,6 +22,12 @@ from ..storage import storage_manager
from ..activity import log_activity
from ..thumbnails import generate_thumbnail, delete_thumbnail
try:
from ..concurrency.atomic import get_atomic_ops
ATOMIC_OPS_AVAILABLE = True
except ImportError:
ATOMIC_OPS_AVAILABLE = False
router = APIRouter(
prefix="/files",
tags=["files"],
@@ -70,51 +76,103 @@ async def upload_file(
else:
parent_folder = None
existing_file = await File.get_or_none(
name=file.filename, parent=parent_folder, owner=current_user, is_deleted=False
)
if existing_file:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="File with this name already exists in the current folder",
)
file_content = await file.read()
file_size = len(file_content)
file_hash = hashlib.sha256(file_content).hexdigest()
if current_user.used_storage_bytes + file_size > current_user.storage_quota_bytes:
raise HTTPException(
status_code=status.HTTP_507_INSUFFICIENT_STORAGE,
detail="Storage quota exceeded",
if ATOMIC_OPS_AVAILABLE:
atomic_ops = get_atomic_ops()
async def check_exists():
return await File.get_or_none(
name=file.filename, parent=parent_folder, owner=current_user, is_deleted=False
)
async def create_file():
file_extension = os.path.splitext(file.filename)[1]
unique_filename = f"{file_hash}{file_extension}"
storage_path = unique_filename
await storage_manager.save_file(current_user.id, storage_path, file_content)
mime_type, _ = mimetypes.guess_type(file.filename)
if not mime_type:
mime_type = "application/octet-stream"
db_file = await File.create(
name=file.filename,
path=storage_path,
size=file_size,
mime_type=mime_type,
file_hash=file_hash,
owner=current_user,
parent=parent_folder,
)
return db_file, storage_path, mime_type
quota_result = await atomic_ops.atomic_quota_check_and_update(
current_user, file_size, lambda u: u.save()
)
if not quota_result.allowed:
raise HTTPException(
status_code=status.HTTP_507_INSUFFICIENT_STORAGE,
detail=f"Storage quota exceeded. Available: {quota_result.remaining} bytes",
)
try:
db_file, storage_path, mime_type = await atomic_ops.atomic_file_create(
current_user,
parent_folder.id if parent_folder else None,
file.filename,
check_exists,
create_file,
)
except FileExistsError as e:
await atomic_ops.atomic_quota_check_and_update(
current_user, -file_size, lambda u: u.save()
)
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=str(e),
)
else:
existing_file = await File.get_or_none(
name=file.filename, parent=parent_folder, owner=current_user, is_deleted=False
)
if existing_file:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="File with this name already exists in the current folder",
)
if current_user.used_storage_bytes + file_size > current_user.storage_quota_bytes:
raise HTTPException(
status_code=status.HTTP_507_INSUFFICIENT_STORAGE,
detail="Storage quota exceeded",
)
file_extension = os.path.splitext(file.filename)[1]
unique_filename = f"{file_hash}{file_extension}"
storage_path = unique_filename
await storage_manager.save_file(current_user.id, storage_path, file_content)
mime_type, _ = mimetypes.guess_type(file.filename)
if not mime_type:
mime_type = "application/octet-stream"
db_file = await File.create(
name=file.filename,
path=storage_path,
size=file_size,
mime_type=mime_type,
file_hash=file_hash,
owner=current_user,
parent=parent_folder,
)
# Generate a unique path for storage
file_extension = os.path.splitext(file.filename)[1]
unique_filename = f"{file_hash}{file_extension}" # Use hash for unique filename
storage_path = unique_filename
# Save file to storage
await storage_manager.save_file(current_user.id, storage_path, file_content)
# Get mime type
mime_type, _ = mimetypes.guess_type(file.filename)
if not mime_type:
mime_type = "application/octet-stream"
# Create file entry in database
db_file = await File.create(
name=file.filename,
path=storage_path,
size=file_size,
mime_type=mime_type,
file_hash=file_hash,
owner=current_user,
parent=parent_folder,
)
current_user.used_storage_bytes += file_size
await current_user.save()
current_user.used_storage_bytes += file_size
await current_user.save()
thumbnail_path = await generate_thumbnail(storage_path, mime_type, current_user.id)
if thumbnail_path:
+36 -2
View File
@@ -183,7 +183,11 @@ async def update_share(
@router.post("/{share_token}/access")
async def access_shared_content(share_token: str, password: Optional[str] = None):
async def access_shared_content(
share_token: str,
password: Optional[str] = None,
subfolder_id: Optional[int] = None,
):
share = await Share.get_or_none(token=share_token)
if not share:
raise HTTPException(
@@ -212,7 +216,37 @@ async def access_shared_content(share_token: str, password: Optional[str] = None
result["file"] = await FileOut.from_tortoise_orm(file)
result["type"] = "file"
elif share.folder_id:
folder = await Folder.get_or_none(id=share.folder_id, is_deleted=False)
# Start with the shared root folder
target_folder_id = share.folder_id
# If subfolder_id is requested, verify it's a descendant of the shared folder
if subfolder_id:
subfolder = await Folder.get_or_none(id=subfolder_id, is_deleted=False)
if not subfolder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Subfolder not found"
)
# Verify hierarchy
current = subfolder
is_descendant = False
while current.parent_id:
if current.parent_id == share.folder_id:
is_descendant = True
break
current = await Folder.get_or_none(id=current.parent_id)
if not current:
break
if not is_descendant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to this folder"
)
target_folder_id = subfolder_id
folder = await Folder.get_or_none(id=target_folder_id, is_deleted=False)
if not folder:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Folder not found"