from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlmodel import Session, select, or_, and_ from typing import List, Optional from datetime import datetime from app.database import get_session from app.models import Snippet, User, Department, SnippetVisibility, SnippetDepartmentLink, UserRole from app.schemas import ( SnippetCreate, SnippetUpdate, SnippetResponse, ) from app.auth import get_current_user router = APIRouter(prefix="/snippets", tags=["Snippets"]) def _normalize_snippet_language(language: str) -> str: value = (language or "").strip() if not value: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Sprache muss angegeben werden" ) return value def user_can_access_snippet(user: User, snippet: Snippet, session: Session) -> bool: """Check if user has access to a snippet based on visibility and department permissions""" # Owner always has access if snippet.owner_id == user.id: return True # Private snippets only for owner if snippet.visibility == SnippetVisibility.PRIVATE: return False # Department snippets - check if enabled for user's departments if snippet.visibility == SnippetVisibility.DEPARTMENT: user_dept_ids = [dept.id for dept in user.departments] if not user_dept_ids: return False # Check if any of user's departments have snippets enabled (master switch) user_depts_with_snippets = [dept for dept in user.departments if dept.snippets_enabled] if not user_depts_with_snippets: return False enabled_dept_ids = [dept.id for dept in user_depts_with_snippets] # Check snippet_department table for enabled departments statement = select(SnippetDepartmentLink).where( and_( SnippetDepartmentLink.snippet_id == snippet.id, SnippetDepartmentLink.department_id.in_(enabled_dept_ids), SnippetDepartmentLink.enabled == True ) ) link = session.exec(statement).first() return link is not None # Organization snippets - all authenticated users if snippet.visibility == SnippetVisibility.ORGANIZATION: return True return False @router.post("/", response_model=SnippetResponse, status_code=status.HTTP_201_CREATED) def create_snippet( snippet_data: SnippetCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Create a new snippet""" language_value = _normalize_snippet_language(snippet_data.language) # Validate department if visibility is department if snippet_data.visibility == "department": if not snippet_data.department_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="department_id required for department visibility" ) # Check if user belongs to that department user_dept_ids = [dept.id for dept in current_user.departments] if snippet_data.department_id not in user_dept_ids and current_user.role != UserRole.SUPERADMIN: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't belong to this department" ) new_snippet = Snippet( title=snippet_data.title, language=language_value, content=snippet_data.content, tags=snippet_data.tags, visibility=SnippetVisibility(snippet_data.visibility), owner_id=current_user.id, department_id=snippet_data.department_id ) session.add(new_snippet) session.commit() session.refresh(new_snippet) response = SnippetResponse.model_validate(new_snippet) response.owner_username = current_user.username return response @router.get("/", response_model=List[SnippetResponse]) def get_snippets( language: Optional[str] = Query(None), tags: Optional[str] = Query(None), search: Optional[str] = Query(None), visibility: Optional[str] = Query(None), session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Get snippets with filters - only those the user can access""" user_dept_ids = [dept.id for dept in current_user.departments] # Build query for accessible snippets conditions = [] # User's own snippets conditions.append(Snippet.owner_id == current_user.id) # Department snippets - need to check snippet_department table if user_dept_ids: # Get enabled snippet IDs for user's departments dept_statement = select(SnippetDepartmentLink.snippet_id).where( and_( SnippetDepartmentLink.department_id.in_(user_dept_ids), SnippetDepartmentLink.enabled == True ) ) enabled_snippet_ids = session.exec(dept_statement).all() if enabled_snippet_ids: conditions.append( and_( Snippet.visibility == SnippetVisibility.DEPARTMENT, Snippet.id.in_(enabled_snippet_ids) ) ) # Organization snippets conditions.append(Snippet.visibility == SnippetVisibility.ORGANIZATION) statement = select(Snippet).where(or_(*conditions)) # Apply filters if language: statement = statement.where(Snippet.language == language) if visibility: statement = statement.where(Snippet.visibility == SnippetVisibility(visibility)) if tags: # Simple tag search - contains any of the tags tag_conditions = [Snippet.tags.contains(tag.strip()) for tag in tags.split(',')] statement = statement.where(or_(*tag_conditions)) if search: # Search in title and content search_pattern = f"%{search}%" statement = statement.where( or_( Snippet.title.ilike(search_pattern), Snippet.content.ilike(search_pattern) ) ) statement = statement.order_by(Snippet.created_at.desc()) snippets = session.exec(statement).all() # Add owner usernames responses = [] for snippet in snippets: response = SnippetResponse.model_validate(snippet) owner = session.get(User, snippet.owner_id) response.owner_username = owner.username if owner else "Unknown" responses.append(response) return responses @router.get("/{snippet_id}", response_model=SnippetResponse) def get_snippet( snippet_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Get a specific snippet""" snippet = session.get(Snippet, snippet_id) if not snippet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Snippet not found" ) if not user_can_access_snippet(current_user, snippet, session): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this snippet" ) response = SnippetResponse.model_validate(snippet) owner = session.get(User, snippet.owner_id) response.owner_username = owner.username if owner else "Unknown" return response @router.put("/{snippet_id}", response_model=SnippetResponse) def update_snippet( snippet_id: int, snippet_data: SnippetUpdate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Update a snippet (only owner can update)""" snippet = session.get(Snippet, snippet_id) if not snippet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Snippet not found" ) # Only owner can update if snippet.owner_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only the owner can update this snippet" ) # Update fields update_data = snippet_data.model_dump(exclude_unset=True) if "language" in update_data and update_data["language"] is not None: update_data["language"] = _normalize_snippet_language(update_data["language"]) # Validate department if visibility is being changed to department if "visibility" in update_data and update_data["visibility"] == "department": dept_id = update_data.get("department_id", snippet.department_id) if not dept_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="department_id required for department visibility" ) user_dept_ids = [dept.id for dept in current_user.departments] if dept_id not in user_dept_ids: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't belong to this department" ) for key, value in update_data.items(): if key == "visibility" and value: setattr(snippet, key, SnippetVisibility(value)) else: setattr(snippet, key, value) snippet.updated_at = datetime.utcnow() session.add(snippet) session.commit() session.refresh(snippet) response = SnippetResponse.model_validate(snippet) response.owner_username = current_user.username return response @router.delete("/{snippet_id}") def delete_snippet( snippet_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Delete a snippet (only owner can delete)""" snippet = session.get(Snippet, snippet_id) if not snippet: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Snippet not found" ) # Only owner can delete if snippet.owner_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only the owner can delete this snippet" ) session.delete(snippet) session.commit() return {"message": "Snippet deleted successfully"}