Improve file listing and fix notification metadata field
Backend:
- Optimize file listing for non-superusers with dedicated CRUD methods
- Add get_visible_for_user and count_visible_for_user for efficient queries
- Move /allowed-types/ and /max-size/ routes before /{file_id} for proper matching
- Rename notification 'metadata' field to 'extra_data' for clarity
- Fix settings export to use get_value() method
Frontend:
- Update NotificationItem interface to use extra_data field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -154,7 +154,7 @@ def export_settings_json(
|
||||
"exported_by": current_user.username,
|
||||
"count": len(settings_list),
|
||||
"settings": {
|
||||
setting.key: setting.value
|
||||
setting.key: setting.get_value()
|
||||
for setting in settings_list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,40 +137,35 @@ def list_files(
|
||||
# Filter by ownership for non-superusers
|
||||
if not current_user.is_superuser:
|
||||
if mine_only:
|
||||
uploaded_by = current_user.id
|
||||
is_public = None
|
||||
files = crud.file_storage.get_multi(
|
||||
db,
|
||||
skip=skip,
|
||||
limit=page_size,
|
||||
uploaded_by=current_user.id,
|
||||
is_public=is_public,
|
||||
content_type=content_type
|
||||
)
|
||||
total = crud.file_storage.count(
|
||||
db,
|
||||
uploaded_by=current_user.id,
|
||||
is_public=is_public,
|
||||
content_type=content_type
|
||||
)
|
||||
else:
|
||||
# Show user's files and public files
|
||||
own_files = crud.file_storage.get_multi(
|
||||
files = crud.file_storage.get_visible_for_user(
|
||||
db,
|
||||
skip=0,
|
||||
limit=1000, # Get all for filtering
|
||||
uploaded_by=current_user.id
|
||||
user_id=current_user.id,
|
||||
skip=skip,
|
||||
limit=page_size,
|
||||
is_public=is_public,
|
||||
content_type=content_type
|
||||
)
|
||||
public_files = crud.file_storage.get_multi(
|
||||
total = crud.file_storage.count_visible_for_user(
|
||||
db,
|
||||
skip=0,
|
||||
limit=1000,
|
||||
is_public=True
|
||||
user_id=current_user.id,
|
||||
is_public=is_public,
|
||||
content_type=content_type
|
||||
)
|
||||
# Combine and deduplicate
|
||||
all_files = {f.id: f for f in own_files}
|
||||
all_files.update({f.id: f for f in public_files})
|
||||
files_list = list(all_files.values())
|
||||
# Sort by created_at desc
|
||||
files_list.sort(key=lambda x: x.created_at, reverse=True)
|
||||
# Paginate
|
||||
total = len(files_list)
|
||||
files = files_list[skip:skip + page_size]
|
||||
|
||||
return {
|
||||
"files": [file_to_schema(f) for f in files],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
uploaded_by = current_user.id if mine_only else None
|
||||
else:
|
||||
uploaded_by = current_user.id if mine_only else None
|
||||
|
||||
@@ -186,7 +181,8 @@ def list_files(
|
||||
total = crud.file_storage.count(
|
||||
db,
|
||||
uploaded_by=uploaded_by,
|
||||
is_public=is_public
|
||||
is_public=is_public,
|
||||
content_type=content_type
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -197,6 +193,29 @@ def list_files(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/allowed-types/", response_model=List[str])
|
||||
def get_allowed_types(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get list of allowed file types for upload.
|
||||
"""
|
||||
return ALLOWED_CONTENT_TYPES
|
||||
|
||||
|
||||
@router.get("/max-size/", response_model=dict)
|
||||
def get_max_size(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get maximum allowed file size.
|
||||
"""
|
||||
return {
|
||||
"max_size_bytes": MAX_FILE_SIZE,
|
||||
"max_size_mb": MAX_FILE_SIZE / (1024 * 1024)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{file_id}", response_model=StoredFileSchema)
|
||||
def get_file(
|
||||
file_id: str,
|
||||
@@ -352,26 +371,3 @@ def delete_file(
|
||||
crud.file_storage.soft_delete(db, id=file_id)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@router.get("/allowed-types/", response_model=List[str])
|
||||
def get_allowed_types(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get list of allowed file types for upload.
|
||||
"""
|
||||
return ALLOWED_CONTENT_TYPES
|
||||
|
||||
|
||||
@router.get("/max-size/", response_model=dict)
|
||||
def get_max_size(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
Get maximum allowed file size.
|
||||
"""
|
||||
return {
|
||||
"max_size_bytes": MAX_FILE_SIZE,
|
||||
"max_size_mb": MAX_FILE_SIZE / (1024 * 1024)
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ router = APIRouter()
|
||||
|
||||
def serialize_notification(db_obj) -> dict:
|
||||
"""Serialize notification for response."""
|
||||
metadata = None
|
||||
if db_obj.metadata:
|
||||
extra_data = None
|
||||
if db_obj.extra_data:
|
||||
try:
|
||||
metadata = json.loads(db_obj.metadata)
|
||||
extra_data = json.loads(db_obj.extra_data)
|
||||
except json.JSONDecodeError:
|
||||
metadata = None
|
||||
extra_data = None
|
||||
|
||||
return {
|
||||
"id": db_obj.id,
|
||||
@@ -37,7 +37,7 @@ def serialize_notification(db_obj) -> dict:
|
||||
"message": db_obj.message,
|
||||
"type": db_obj.type,
|
||||
"link": db_obj.link,
|
||||
"metadata": metadata,
|
||||
"extra_data": extra_data,
|
||||
"is_read": db_obj.is_read,
|
||||
"created_at": db_obj.created_at,
|
||||
"read_at": db_obj.read_at
|
||||
@@ -239,6 +239,6 @@ def broadcast_notification(
|
||||
message=notification_in.message,
|
||||
type=notification_in.type,
|
||||
link=notification_in.link,
|
||||
metadata=notification_in.metadata
|
||||
extra_data=notification_in.extra_data
|
||||
)
|
||||
return {"sent_to": count}
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, BinaryIO
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_
|
||||
|
||||
from app.models.file import StoredFile
|
||||
from app.schemas.file import FileCreate, FileUpdate, ALLOWED_CONTENT_TYPES, MAX_FILE_SIZE
|
||||
@@ -132,12 +133,36 @@ class CRUDFile:
|
||||
|
||||
return query.order_by(StoredFile.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
def get_visible_for_user(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
user_id: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
is_public: Optional[bool] = None,
|
||||
content_type: Optional[str] = None
|
||||
) -> List[StoredFile]:
|
||||
"""Get files visible to a user (own + public) with optional filtering."""
|
||||
query = db.query(StoredFile).filter(
|
||||
StoredFile.is_deleted == False,
|
||||
or_(StoredFile.uploaded_by == user_id, StoredFile.is_public == True)
|
||||
)
|
||||
|
||||
if is_public is not None:
|
||||
query = query.filter(StoredFile.is_public == is_public)
|
||||
if content_type:
|
||||
query = query.filter(StoredFile.content_type.like(f"{content_type}%"))
|
||||
|
||||
return query.order_by(StoredFile.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
def count(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
uploaded_by: Optional[str] = None,
|
||||
is_public: Optional[bool] = None
|
||||
is_public: Optional[bool] = None,
|
||||
content_type: Optional[str] = None
|
||||
) -> int:
|
||||
"""Count files with optional filtering."""
|
||||
query = db.query(StoredFile).filter(StoredFile.is_deleted == False)
|
||||
@@ -146,9 +171,31 @@ class CRUDFile:
|
||||
query = query.filter(StoredFile.uploaded_by == uploaded_by)
|
||||
if is_public is not None:
|
||||
query = query.filter(StoredFile.is_public == is_public)
|
||||
if content_type:
|
||||
query = query.filter(StoredFile.content_type.like(f"{content_type}%"))
|
||||
|
||||
return query.count()
|
||||
|
||||
def count_visible_for_user(
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
user_id: str,
|
||||
is_public: Optional[bool] = None,
|
||||
content_type: Optional[str] = None
|
||||
) -> int:
|
||||
"""Count files visible to a user (own + public) with optional filtering."""
|
||||
query = db.query(StoredFile).filter(
|
||||
StoredFile.is_deleted == False,
|
||||
or_(StoredFile.uploaded_by == user_id, StoredFile.is_public == True)
|
||||
)
|
||||
|
||||
if is_public is not None:
|
||||
query = query.filter(StoredFile.is_public == is_public)
|
||||
if content_type:
|
||||
query = query.filter(StoredFile.content_type.like(f"{content_type}%"))
|
||||
|
||||
return query.count()
|
||||
def create(
|
||||
self,
|
||||
db: Session,
|
||||
|
||||
@@ -244,7 +244,7 @@ export interface NotificationItem {
|
||||
message: string | null;
|
||||
type: 'info' | 'success' | 'warning' | 'error' | 'system';
|
||||
link: string | null;
|
||||
metadata: Record<string, unknown> | null;
|
||||
extra_data: Record<string, unknown> | null;
|
||||
is_read: boolean;
|
||||
created_at: string;
|
||||
read_at: string | null;
|
||||
|
||||
Reference in New Issue
Block a user