import logging from typing import List, Optional from fastapi import APIRouter, HTTPException, Query from app.core.database import execute_query from datetime import datetime logger = logging.getLogger(__name__) router = APIRouter() # ============================================================================ # SAGER - CRUD Operations # ============================================================================ @router.get("/sag") async def list_sager( status: Optional[str] = Query(None), tag: Optional[str] = Query(None), customer_id: Optional[int] = Query(None), ansvarlig_bruger_id: Optional[int] = Query(None), ): """List all cases with optional filtering.""" try: query = "SELECT * FROM sag_sager WHERE deleted_at IS NULL" params = [] if status: query += " AND status = %s" params.append(status) if customer_id: query += " AND customer_id = %s" params.append(customer_id) if ansvarlig_bruger_id: query += " AND ansvarlig_bruger_id = %s" params.append(ansvarlig_bruger_id) query += " ORDER BY created_at DESC" cases = execute_query(query, tuple(params)) # If tag filter, filter in Python after fetch if tag: case_ids = [case['id'] for case in cases] if case_ids: tag_query = "SELECT sag_id FROM sag_tags WHERE tag_navn = %s AND deleted_at IS NULL" tagged_cases = execute_query(tag_query, (tag,)) tagged_ids = set(t['sag_id'] for t in tagged_cases) cases = [c for c in cases if c['id'] in tagged_ids] return cases except Exception as e: logger.error("❌ Error listing cases: %s", e) raise HTTPException(status_code=500, detail="Failed to list cases") @router.post("/sag") async def create_sag(data: dict): """Create a new case.""" try: if not data.get('titel'): raise HTTPException(status_code=400, detail="titel is required") if not data.get('customer_id'): raise HTTPException(status_code=400, detail="customer_id is required") query = """ INSERT INTO sag_sager (titel, beskrivelse, type, status, customer_id, ansvarlig_bruger_id, deadline) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING * """ params = ( data.get('titel'), data.get('beskrivelse', ''), data.get('type', 'ticket'), data.get('status', 'åben'), data.get('customer_id'), data.get('ansvarlig_bruger_id'), data.get('deadline'), ) result = execute_query(query, params) if result: logger.info("✅ Case created: %s", result[0]['id']) return result[0] raise HTTPException(status_code=500, detail="Failed to create case") except Exception as e: logger.error("❌ Error creating case: %s", e) raise HTTPException(status_code=500, detail="Failed to create case") @router.get("/sag/{sag_id}") async def get_sag(sag_id: int): """Get a specific case.""" try: query = "SELECT * FROM sag_sager WHERE id = %s AND deleted_at IS NULL" result = execute_query(query, (sag_id,)) if not result: raise HTTPException(status_code=404, detail="Case not found") return result[0] except HTTPException: raise except Exception as e: logger.error("❌ Error getting case: %s", e) raise HTTPException(status_code=500, detail="Failed to get case") @router.patch("/sag/{sag_id}") async def update_sag(sag_id: int, updates: dict): """Update a case.""" try: # Check if case exists check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) if not check: raise HTTPException(status_code=404, detail="Case not found") # Build dynamic update query allowed_fields = ['titel', 'beskrivelse', 'type', 'status', 'ansvarlig_bruger_id', 'deadline'] set_clauses = [] params = [] for field in allowed_fields: if field in updates: set_clauses.append(f"{field} = %s") params.append(updates[field]) if not set_clauses: raise HTTPException(status_code=400, detail="No valid fields to update") params.append(sag_id) query = f"UPDATE sag_sager SET {', '.join(set_clauses)} WHERE id = %s RETURNING *" result = execute_query(query, tuple(params)) if result: logger.info("✅ Case updated: %s", sag_id) return result[0] raise HTTPException(status_code=500, detail="Failed to update case") except HTTPException: raise except Exception as e: logger.error("❌ Error updating case: %s", e) raise HTTPException(status_code=500, detail="Failed to update case") @router.delete("/sag/{sag_id}") async def delete_sag(sag_id: int): """Soft-delete a case.""" try: check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) if not check: raise HTTPException(status_code=404, detail="Case not found") query = "UPDATE sag_sager SET deleted_at = NOW() WHERE id = %s RETURNING id" result = execute_query(query, (sag_id,)) if result: logger.info("✅ Case soft-deleted: %s", sag_id) return {"status": "deleted", "id": sag_id} raise HTTPException(status_code=500, detail="Failed to delete case") except HTTPException: raise except Exception as e: logger.error("❌ Error deleting case: %s", e) raise HTTPException(status_code=500, detail="Failed to delete case") # ============================================================================ # RELATIONER - Case Relations # ============================================================================ @router.get("/sag/{sag_id}/relationer") async def get_relationer(sag_id: int): """Get all relations for a case.""" try: # Check if case exists check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) if not check: raise HTTPException(status_code=404, detail="Case not found") query = """ SELECT sr.*, ss_kilde.titel as kilde_titel, ss_mål.titel as mål_titel FROM sag_relationer sr JOIN sag_sager ss_kilde ON sr.kilde_sag_id = ss_kilde.id JOIN sag_sager ss_mål ON sr.målsag_id = ss_mål.id WHERE (sr.kilde_sag_id = %s OR sr.målsag_id = %s) AND sr.deleted_at IS NULL ORDER BY sr.created_at DESC """ result = execute_query(query, (sag_id, sag_id)) return result except HTTPException: raise except Exception as e: logger.error("❌ Error getting relations: %s", e) raise HTTPException(status_code=500, detail="Failed to get relations") @router.post("/sag/{sag_id}/relationer") async def create_relation(sag_id: int, data: dict): """Add a relation to another case.""" try: if not data.get('målsag_id') or not data.get('relationstype'): raise HTTPException(status_code=400, detail="målsag_id and relationstype required") målsag_id = data.get('målsag_id') relationstype = data.get('relationstype') # Validate both cases exist check1 = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) check2 = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (målsag_id,)) if not check1 or not check2: raise HTTPException(status_code=404, detail="One or both cases not found") query = """ INSERT INTO sag_relationer (kilde_sag_id, målsag_id, relationstype) VALUES (%s, %s, %s) RETURNING * """ result = execute_query(query, (sag_id, målsag_id, relationstype)) if result: logger.info("✅ Relation created: %s -> %s (%s)", sag_id, målsag_id, relationstype) return result[0] raise HTTPException(status_code=500, detail="Failed to create relation") except HTTPException: raise except Exception as e: logger.error("❌ Error creating relation: %s", e) raise HTTPException(status_code=500, detail="Failed to create relation") @router.delete("/sag/{sag_id}/relationer/{relation_id}") async def delete_relation(sag_id: int, relation_id: int): """Soft-delete a relation.""" try: check = execute_query( "SELECT id FROM sag_relationer WHERE id = %s AND deleted_at IS NULL AND (kilde_sag_id = %s OR målsag_id = %s)", (relation_id, sag_id, sag_id) ) if not check: raise HTTPException(status_code=404, detail="Relation not found") query = "UPDATE sag_relationer SET deleted_at = NOW() WHERE id = %s RETURNING id" result = execute_query(query, (relation_id,)) if result: logger.info("✅ Relation soft-deleted: %s", relation_id) return {"status": "deleted", "id": relation_id} raise HTTPException(status_code=500, detail="Failed to delete relation") except HTTPException: raise except Exception as e: logger.error("❌ Error deleting relation: %s", e) raise HTTPException(status_code=500, detail="Failed to delete relation") # ============================================================================ # TAGS - Case Tags # ============================================================================ @router.get("/sag/{sag_id}/tags") async def get_tags(sag_id: int): """Get all tags for a case.""" try: check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) if not check: raise HTTPException(status_code=404, detail="Case not found") query = "SELECT * FROM sag_tags WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC" result = execute_query(query, (sag_id,)) return result except HTTPException: raise except Exception as e: logger.error("❌ Error getting tags: %s", e) raise HTTPException(status_code=500, detail="Failed to get tags") @router.post("/sag/{sag_id}/tags") async def add_tag(sag_id: int, data: dict): """Add a tag to a case.""" try: if not data.get('tag_navn'): raise HTTPException(status_code=400, detail="tag_navn is required") check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,)) if not check: raise HTTPException(status_code=404, detail="Case not found") query = """ INSERT INTO sag_tags (sag_id, tag_navn) VALUES (%s, %s) RETURNING * """ result = execute_query(query, (sag_id, data.get('tag_navn'))) if result: logger.info("✅ Tag added: %s -> %s", sag_id, data.get('tag_navn')) return result[0] raise HTTPException(status_code=500, detail="Failed to add tag") except HTTPException: raise except Exception as e: logger.error("❌ Error adding tag: %s", e) raise HTTPException(status_code=500, detail="Failed to add tag") @router.delete("/sag/{sag_id}/tags/{tag_id}") async def delete_tag(sag_id: int, tag_id: int): """Soft-delete a tag.""" try: check = execute_query( "SELECT id FROM sag_tags WHERE id = %s AND sag_id = %s AND deleted_at IS NULL", (tag_id, sag_id) ) if not check: raise HTTPException(status_code=404, detail="Tag not found") query = "UPDATE sag_tags SET deleted_at = NOW() WHERE id = %s RETURNING id" result = execute_query(query, (tag_id,)) if result: logger.info("✅ Tag soft-deleted: %s", tag_id) return {"status": "deleted", "id": tag_id} raise HTTPException(status_code=500, detail="Failed to delete tag") except HTTPException: raise except Exception as e: logger.error("❌ Error deleting tag: %s", e) raise HTTPException(status_code=500, detail="Failed to delete tag")