Post Typen Verwalten + Strategy weight

This commit is contained in:
2026-02-14 14:48:03 +01:00
parent 1ebf50ab04
commit 31150000fd
14 changed files with 2624 additions and 43 deletions

View File

@@ -34,7 +34,7 @@ from src.services.email_service import (
from src.services.background_jobs import (
job_manager, JobType, JobStatus,
run_post_scraping, run_profile_analysis, run_post_categorization, run_post_type_analysis,
run_full_analysis_pipeline
run_full_analysis_pipeline, run_post_recategorization
)
from src.services.storage_service import storage
@@ -2948,6 +2948,310 @@ async def employee_strategy_page(request: Request):
})
# ============================================================================
# EMPLOYEE POST TYPES MANAGEMENT
# ============================================================================
@user_router.get("/post-types/manage")
async def employee_post_types_page(request: Request):
"""Employee post types management page with strategy weight configuration."""
session = require_user_session(request)
if not session:
return RedirectResponse(url="/login", status_code=302)
if session.account_type != "employee":
raise HTTPException(status_code=403, detail="Only employees can access this page")
try:
user_id = UUID(session.user_id)
# Get all active post types for this employee
post_types = await db.get_post_types(user_id, active_only=True)
# Count posts for each post type and convert to JSON-serializable format
post_types_with_counts = []
for pt in post_types:
posts = await db.get_posts_by_type(user_id, pt.id)
post_types_with_counts.append({
"post_type": {
"id": str(pt.id),
"name": pt.name,
"description": pt.description,
"strategy_weight": pt.strategy_weight,
"is_active": pt.is_active
},
"post_count": len(posts)
})
# Check if company strategy exists
company_strategy = None
has_strategy = False
if session.company_id:
company_id = UUID(session.company_id)
company = await db.get_company(company_id)
if company and company.company_strategy:
company_strategy = company.company_strategy
has_strategy = True
profile_picture = await get_user_avatar(session, user_id)
# Convert to JSON string for JavaScript
import json
post_types_json = json.dumps(post_types_with_counts)
logger.info(f"Generated JSON for {len(post_types_with_counts)} post types")
logger.info(f"JSON length: {len(post_types_json)} characters")
logger.info(f"JSON preview: {post_types_json[:200] if len(post_types_json) > 200 else post_types_json}")
return templates.TemplateResponse("employee_post_types.html", {
"request": request,
"page": "post_types",
"session": session,
"post_types_with_counts": post_types_with_counts,
"post_types_json": post_types_json,
"has_strategy": has_strategy,
"company_strategy": company_strategy,
"profile_picture": profile_picture
})
except Exception as e:
logger.error(f"Error loading employee post types page: {e}")
profile_picture = await get_user_avatar(session, UUID(session.user_id))
import json
return templates.TemplateResponse("employee_post_types.html", {
"request": request,
"page": "post_types",
"session": session,
"post_types_with_counts": [],
"post_types_json": json.dumps([]),
"has_strategy": False,
"company_strategy": None,
"error": str(e),
"profile_picture": profile_picture
})
@user_router.post("/api/employee/post-types")
async def create_employee_post_type(request: Request, background_tasks: BackgroundTasks):
"""Create a new post type for an employee."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "employee":
return JSONResponse({"error": "Only employees can create post types"}, status_code=403)
try:
data = await request.json()
user_id = UUID(session.user_id)
# Validate required fields
name_raw = data.get("name")
name = name_raw.strip() if name_raw else ""
if not name or len(name) < 3:
return JSONResponse({"error": "Name must be at least 3 characters"}, status_code=400)
description_raw = data.get("description")
description = description_raw.strip() if description_raw else ""
strategy_weight = float(data.get("strategy_weight", 0.5))
# Validate strategy_weight range
if not (0.0 <= strategy_weight <= 1.0):
return JSONResponse({"error": "Strategy weight must be between 0.0 and 1.0"}, status_code=400)
# Check if an inactive post type with this name already exists
existing_inactive = None
try:
all_post_types = await db.get_post_types(user_id, active_only=False)
existing_inactive = next((pt for pt in all_post_types if pt.name.lower() == name.lower() and not pt.is_active), None)
except Exception as check_error:
logger.warning(f"Could not check for inactive post types: {check_error}")
if existing_inactive:
# Reactivate the existing post type instead of creating a new one
await db.update_post_type(existing_inactive.id, {
"is_active": True,
"description": description if description else existing_inactive.description,
"strategy_weight": strategy_weight
})
created_post_type = await db.get_post_type(existing_inactive.id)
logger.info(f"Reactivated post type '{name}' for user {user_id}")
else:
# Create new post type
from src.database.models import PostType
post_type = PostType(
user_id=user_id,
name=name,
description=description if description else None,
strategy_weight=strategy_weight,
is_active=True
)
created_post_type = await db.create_post_type(post_type)
logger.info(f"Created post type '{name}' for user {user_id}")
return JSONResponse({
"success": True,
"post_type": {
"id": str(created_post_type.id),
"name": created_post_type.name,
"description": created_post_type.description,
"strategy_weight": created_post_type.strategy_weight
}
})
except Exception as e:
logger.error(f"Error creating post type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.put("/api/employee/post-types/{post_type_id}")
async def update_employee_post_type(request: Request, post_type_id: str):
"""Update an existing post type."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "employee":
return JSONResponse({"error": "Only employees can update post types"}, status_code=403)
try:
user_id = UUID(session.user_id)
pt_id = UUID(post_type_id)
# Check ownership
post_type = await db.get_post_type(pt_id)
if not post_type or post_type.user_id != user_id:
return JSONResponse({"error": "Post type not found or access denied"}, status_code=404)
data = await request.json()
updates = {}
# Update name if provided
if "name" in data:
name_raw = data["name"]
name = name_raw.strip() if name_raw else ""
if not name or len(name) < 3:
return JSONResponse({"error": "Name must be at least 3 characters"}, status_code=400)
updates["name"] = name
# Update description if provided
if "description" in data:
desc_raw = data["description"]
updates["description"] = desc_raw.strip() if desc_raw else None
# Update strategy_weight if provided
if "strategy_weight" in data:
strategy_weight = float(data["strategy_weight"])
if not (0.0 <= strategy_weight <= 1.0):
return JSONResponse({"error": "Strategy weight must be between 0.0 and 1.0"}, status_code=400)
updates["strategy_weight"] = strategy_weight
# Apply updates
if updates:
await db.update_post_type(pt_id, updates)
return JSONResponse({"success": True})
except Exception as e:
logger.error(f"Error updating post type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.delete("/api/employee/post-types/{post_type_id}")
async def delete_employee_post_type(request: Request, post_type_id: str, background_tasks: BackgroundTasks):
"""Soft delete a post type (set is_active = False)."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "employee":
return JSONResponse({"error": "Only employees can delete post types"}, status_code=403)
try:
user_id = UUID(session.user_id)
pt_id = UUID(post_type_id)
# Check ownership
post_type = await db.get_post_type(pt_id)
if not post_type or post_type.user_id != user_id:
return JSONResponse({"error": "Post type not found or access denied"}, status_code=404)
# Count affected posts
posts = await db.get_posts_by_type(user_id, pt_id)
affected_count = len(posts)
# Soft delete
await db.update_post_type(pt_id, {"is_active": False})
logger.info(f"Deleted post type '{post_type.name}' for user {user_id}")
return JSONResponse({
"success": True,
"affected_posts": affected_count
})
except Exception as e:
logger.error(f"Error deleting post type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.post("/api/employee/post-types/save-all")
async def save_all_and_reanalyze(request: Request, background_tasks: BackgroundTasks):
"""Save all changes and conditionally trigger re-categorization based on structural changes."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "employee":
return JSONResponse({"error": "Only employees can trigger re-analysis"}, status_code=403)
try:
data = await request.json()
has_structural_changes = data.get("has_structural_changes", False)
user_id = UUID(session.user_id)
user_id_str = str(user_id)
# Only trigger re-categorization and analysis if there were structural changes
if has_structural_changes:
# Create background job for post re-categorization (ALL posts)
categorization_job = job_manager.create_job(
job_type=JobType.POST_CATEGORIZATION,
user_id=user_id_str
)
# Create background job for post type analysis
analysis_job = job_manager.create_job(
job_type=JobType.POST_TYPE_ANALYSIS,
user_id=user_id_str
)
# Start background tasks
background_tasks.add_task(run_post_recategorization, user_id, categorization_job.id)
background_tasks.add_task(run_post_type_analysis, user_id, analysis_job.id)
logger.info(f"Started re-analysis jobs for user {user_id}: categorization={categorization_job.id}, analysis={analysis_job.id}")
return JSONResponse({
"success": True,
"recategorized": True,
"categorization_job_id": str(categorization_job.id),
"analysis_job_id": str(analysis_job.id)
})
else:
logger.info(f"No structural changes for user {user_id}, skipping re-analysis")
return JSONResponse({
"success": True,
"recategorized": False,
"message": "Nur Weight-Updates, keine Rekategorisierung notwendig"
})
except Exception as e:
logger.error(f"Error in save-all: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.post("/api/company/invite")
async def send_company_invitation(request: Request):
"""Send invitation to a new employee."""