DGSoft 93b98cfb5c Initial commit: Team Chat System with Code Snippet Library
- Complete chat application similar to Microsoft Teams
- Code snippet library with syntax highlighting
- Real-time messaging with WebSockets
- File upload with Office integration
- Department-based permissions
- Dark/Light theme support
- Production deployment with SSL/Reverse Proxy
- Docker containerization
- PostgreSQL database with SQLModel ORM
2025-12-09 22:25:03 +01:00

304 lines
10 KiB
Python

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
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:
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"}