234 lines
7.4 KiB
Python
234 lines
7.4 KiB
Python
"""
|
|
LinkSyncServer - Collection CRUD Endpoints with SQLAlchemy
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func, and_, or_, exists
|
|
from typing import List, Optional
|
|
import uuid
|
|
import logging
|
|
|
|
from models.base import Base, Bookmark, Collection, AuditLog, get_engine, sessionmaker
|
|
from pydantic import BaseModel, Field
|
|
import os
|
|
|
|
router = APIRouter(prefix="/api/collections", tags=["Collections"])
|
|
|
|
# Logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CollectionCreate(BaseModel):
|
|
name: str = Field(..., description="Collection name")
|
|
description: Optional[str] = Field(None, max_length=1024, description="Collection description")
|
|
query_type: str = Field(default="static", description="Static or dynamic collection")
|
|
query_expression: Optional[dict] = Field(None, description="Query expression for dynamic collections")
|
|
is_public: bool = Field(default=False, description="Is collection public")
|
|
tags: Optional[List[str]] = Field(default_factory=list, description="Collection tags")
|
|
|
|
|
|
class CollectionUpdate(BaseModel):
|
|
name: Optional[str] = Field(None, max_length=255)
|
|
description: Optional[str] = Field(None, max_length=1024)
|
|
query_type: Optional[str] = Field(None)
|
|
query_expression: Optional[dict] = Field(None)
|
|
is_public: Optional[bool] = None
|
|
tags: Optional[List[str]] = Field(None)
|
|
|
|
|
|
class CollectionResponse(BaseModel):
|
|
id: str
|
|
name: str
|
|
description: Optional[str]
|
|
query_type: str
|
|
query_expression: Optional[dict]
|
|
is_public: bool
|
|
created_at: str
|
|
updated_at: str
|
|
tags: List[str]
|
|
|
|
|
|
def get_db():
|
|
"""Get database session."""
|
|
db_session = sessionmaker(get_engine())()
|
|
return db_session
|
|
|
|
|
|
def get_current_user(request: Request):
|
|
"""Get current authenticated user."""
|
|
SECRET_KEY = os.environ.get("SECRET_KEY")
|
|
|
|
auth_header = request.headers.get("Authorization") or request.headers.get("authorization")
|
|
|
|
if auth_header and auth_header.startswith("Bearer "):
|
|
token = auth_header[7:]
|
|
try:
|
|
import jwt
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
|
return {"username": payload.get("sub"), "id": payload.get("sub")}
|
|
except Exception:
|
|
pass
|
|
|
|
return {"username": "guest"}
|
|
|
|
|
|
class CollectionManager:
|
|
"""Collection management helper."""
|
|
|
|
@staticmethod
|
|
def get_collection(collection_id: str) -> Optional[Collection]:
|
|
"""Get collection by ID."""
|
|
db = get_db()
|
|
try:
|
|
collection = db.query(Collection).filter(Collection.id == collection_id).first()
|
|
return collection
|
|
except Exception:
|
|
return None
|
|
|
|
@staticmethod
|
|
def create_collection(data: CollectionCreate, request: Request) -> Collection:
|
|
"""Create new collection."""
|
|
db = get_db()
|
|
|
|
collection = Collection(
|
|
name=data.name,
|
|
description=data.description,
|
|
query_type=data.query_type,
|
|
query_expression=data.query_expression,
|
|
is_public=data.is_public,
|
|
tags=TagCollection(tags=data.tags or []),
|
|
)
|
|
|
|
db.add(collection)
|
|
db.commit()
|
|
db.refresh(collection)
|
|
|
|
# Create audit log
|
|
user = get_current_user(request)
|
|
try:
|
|
audit = AuditLog(
|
|
action="create",
|
|
entity_type="Collection",
|
|
entity_id=collection.id,
|
|
old_value=None,
|
|
new_value=collection.dict(),
|
|
user_id=user.get("id")
|
|
)
|
|
db.add(audit)
|
|
db.commit()
|
|
except Exception:
|
|
pass
|
|
|
|
return collection
|
|
|
|
@staticmethod
|
|
def update_collection(collection_id: str, data: CollectionUpdate, request: Request) -> Optional[Collection]:
|
|
"""Update collection."""
|
|
db = get_db()
|
|
|
|
collection = db.query(Collection).filter(Collection.id == collection_id).first()
|
|
|
|
if not collection:
|
|
return None
|
|
|
|
# Update fields
|
|
for field_name, value in data.dict().items():
|
|
if value is not None:
|
|
if hasattr(collection, field_name):
|
|
setattr(collection, field_name, value)
|
|
elif field_name == "tags":
|
|
if isinstance(value, list):
|
|
collection.tags.add(*value)
|
|
else:
|
|
collection.tags.update(str(value))
|
|
|
|
db.commit()
|
|
db.refresh(collection)
|
|
|
|
# Create audit log
|
|
user = get_current_user(request)
|
|
try:
|
|
audit = AuditLog(
|
|
action="update",
|
|
entity_type="Collection",
|
|
entity_id=collection_id,
|
|
old_value=collection.dict(),
|
|
new_value=collection.dict(),
|
|
user_id=user.get("id")
|
|
)
|
|
db.add(audit)
|
|
db.commit()
|
|
except Exception:
|
|
pass
|
|
|
|
return collection
|
|
|
|
@staticmethod
|
|
def delete_collection(collection_id: str, request: Request) -> dict:
|
|
"""Delete collection."""
|
|
db = get_db()
|
|
|
|
collection = db.query(Collection).filter(Collection.id == collection_id).first()
|
|
|
|
if not collection:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found")
|
|
|
|
db.delete(collection)
|
|
db.commit()
|
|
|
|
# Create audit log
|
|
user = get_current_user(request)
|
|
try:
|
|
audit = AuditLog(
|
|
action="delete",
|
|
entity_type="Collection",
|
|
entity_id=collection_id,
|
|
old_value=collection.dict(),
|
|
new_value=None,
|
|
user_id=user.get("id")
|
|
)
|
|
db.add(audit)
|
|
db.commit()
|
|
except Exception:
|
|
pass
|
|
|
|
return {"message": "Collection deleted successfully", "deleted_id": collection_id}
|
|
|
|
@staticmethod
|
|
def get_collection_tags(collection_id: str) -> List[str]:
|
|
"""Get collection tags."""
|
|
db = get_db()
|
|
|
|
collection = db.query(Collection).filter(Collection.id == collection_id).first()
|
|
|
|
if not collection:
|
|
return []
|
|
|
|
return list(collection.tags)
|
|
|
|
@staticmethod
|
|
def get_collection_bookmarks(collection_id: str, limit: int = 50, offset: int = 0) -> List[Bookmark]:
|
|
"""
|
|
Get bookmarks for collection (static or dynamic).
|
|
|
|
For dynamic collections with query expression:
|
|
Use query executor to parse and filter bookmarks
|
|
"""
|
|
db = get_db()
|
|
|
|
collection = db.query(Collection).filter(Collection.id == collection_id).first()
|
|
|
|
if not collection:
|
|
return []
|
|
|
|
if collection.query_type == "static":
|
|
# Static collection: get all bookmarks
|
|
bookmarks = db.query(Bookmark).filter(Bookmark.collection_id == collection_id).limit(limit).offset(offset).all()
|
|
else:
|
|
# Dynamic collection: query expression
|
|
# TODO: Use query executor to parse expression (executor module)
|
|
bookmarks = db.query(Bookmark).limit(limit).offset(offset).all()
|
|
|
|
return bookmarks
|