mirror of
https://github.com/OHV-IT/collabrix.git
synced 2025-12-15 16:48:36 +01:00
- 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
304 lines
10 KiB
Python
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"}
|
|
|