added manual post add

This commit is contained in:
2026-04-02 10:39:07 +02:00
parent 252edcd001
commit fe15a5ab89
5 changed files with 744 additions and 7 deletions

View File

@@ -1581,6 +1581,78 @@ async def api_categorize_post(request: Request):
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.post("/api/post-types/manual-posts")
async def api_add_manual_post_for_post_types(request: Request):
"""Add a manual post from the post types page and optionally assign a type immediately."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
try:
data = await request.json()
user_id = UUID(session.user_id)
post_text = (data.get("post_text") or "").strip()
post_type_id_raw = data.get("post_type_id")
if len(post_text) < 20:
return JSONResponse({"error": "Post muss mindestens 20 Zeichen lang sein"}, status_code=400)
post_type_id = UUID(post_type_id_raw) if post_type_id_raw else None
if post_type_id:
post_type = await db.get_post_type(post_type_id)
if not post_type or post_type.user_id != user_id:
return JSONResponse({"error": "Post-Typ nicht gefunden oder kein Zugriff"}, status_code=404)
from uuid import uuid4
from src.database.models import LinkedInPost
manual_post = LinkedInPost(
user_id=user_id,
post_text=post_text,
post_url=f"manual://{uuid4()}",
post_date=datetime.now(timezone.utc),
post_type_id=post_type_id,
classification_method="manual",
classification_confidence=1.0,
raw_data={"source": "manual", "manual_entry": True}
)
saved_posts = await db.save_linkedin_posts([manual_post])
saved = saved_posts[0] if saved_posts else None
return JSONResponse({
"success": True,
"post": {
"id": str(saved.id) if saved and saved.id else None,
"post_text": saved.post_text if saved else post_text,
"post_date": saved.post_date.isoformat() if saved and saved.post_date else None,
"post_type_id": str(saved.post_type_id) if saved and saved.post_type_id else None,
}
})
except Exception as e:
logger.error(f"Error adding manual post from post types page: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.delete("/api/post-types/posts/{post_id}")
async def api_delete_post_from_post_types_page(request: Request, post_id: str):
"""Delete a post from the main post types page."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
try:
user_id = UUID(session.user_id)
post = await db.get_linkedin_post(UUID(post_id))
if not post or post.user_id != user_id:
return JSONResponse({"error": "Post nicht gefunden oder kein Zugriff"}, status_code=404)
await db.delete_linkedin_post(UUID(post_id))
return JSONResponse({"success": True})
except Exception as e:
logger.error(f"Error deleting post from post types page: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.get("/api/job-updates")
async def job_updates_sse(request: Request):
"""Server-Sent Events endpoint for job updates (Redis pub/sub — works across workers)."""
@@ -4259,6 +4331,250 @@ async def company_save_all_employee_post_types(request: Request, background_task
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.get("/api/employee/post-types/{post_type_id}/posts")
async def employee_list_post_type_posts(request: Request, post_type_id: str):
"""List posts assigned to a 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 access this endpoint"}, status_code=403)
try:
user_id = UUID(session.user_id)
pt_id = UUID(post_type_id)
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)
posts = await db.get_posts_by_type(user_id, pt_id)
return JSONResponse({
"success": True,
"posts": [
{
"id": str(p.id),
"post_text": p.post_text,
"post_date": p.post_date.isoformat() if p.post_date else None,
"post_url": p.post_url,
"classification_method": p.classification_method,
}
for p in posts
]
})
except Exception as e:
logger.error(f"Error listing employee post type posts: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.post("/api/employee/post-types/posts")
async def employee_add_manual_post_to_type(request: Request):
"""Add a manual post and assign it to a 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 add posts here"}, status_code=403)
try:
data = await request.json()
user_id = UUID(session.user_id)
pt_id = UUID(data.get("post_type_id"))
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)
post_text = (data.get("post_text") or "").strip()
if len(post_text) < 20:
return JSONResponse({"error": "Post muss mindestens 20 Zeichen lang sein"}, status_code=400)
from uuid import uuid4
from src.database.models import LinkedInPost
manual_post = LinkedInPost(
user_id=user_id,
post_text=post_text,
post_url=f"manual://{uuid4()}",
post_date=datetime.now(timezone.utc),
post_type_id=pt_id,
classification_method="manual",
classification_confidence=1.0,
raw_data={"source": "manual", "manual_entry": True}
)
saved_posts = await db.save_linkedin_posts([manual_post])
saved = saved_posts[0] if saved_posts else None
return JSONResponse({
"success": True,
"post": {
"id": str(saved.id) if saved and saved.id else None,
"post_text": saved.post_text if saved else post_text,
"post_date": saved.post_date.isoformat() if saved and saved.post_date else None,
}
})
except Exception as e:
logger.error(f"Error adding manual post for employee post type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.delete("/api/employee/post-types/{post_type_id}/posts/{post_id}")
async def employee_delete_post_from_type(request: Request, post_type_id: str, post_id: str):
"""Delete a post assigned to a 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 delete posts here"}, status_code=403)
try:
user_id = UUID(session.user_id)
pt_id = UUID(post_type_id)
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)
post = await db.get_linkedin_post(UUID(post_id))
if not post or post.user_id != user_id or post.post_type_id != pt_id:
return JSONResponse({"error": "Post not found or access denied"}, status_code=404)
await db.delete_linkedin_post(UUID(post_id))
return JSONResponse({"success": True})
except Exception as e:
logger.error(f"Error deleting employee post from type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.get("/api/company/manage/post-types/{post_type_id}/posts")
async def company_list_employee_post_type_posts(request: Request, post_type_id: str, employee_id: str):
"""Company lists posts assigned to an employee's post type."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "company" or not session.company_id:
return JSONResponse({"error": "Only company accounts can access this endpoint"}, status_code=403)
try:
emp_id = UUID(employee_id)
emp_user = await db.get_user(emp_id)
if not emp_user or str(emp_user.company_id) != session.company_id:
return JSONResponse({"error": "Employee not found or not in company"}, status_code=403)
pt_id = UUID(post_type_id)
post_type = await db.get_post_type(pt_id)
if not post_type or post_type.user_id != emp_id:
return JSONResponse({"error": "Post type not found or access denied"}, status_code=404)
posts = await db.get_posts_by_type(emp_id, pt_id)
return JSONResponse({
"success": True,
"posts": [
{
"id": str(p.id),
"post_text": p.post_text,
"post_date": p.post_date.isoformat() if p.post_date else None,
"post_url": p.post_url,
"classification_method": p.classification_method,
}
for p in posts
]
})
except Exception as e:
logger.error(f"Error listing company-managed employee post type posts: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.post("/api/company/manage/post-types/posts")
async def company_add_manual_post_to_employee_type(request: Request):
"""Company adds a manual post for an employee and assigns it to a post type."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "company" or not session.company_id:
return JSONResponse({"error": "Only company accounts can add posts here"}, status_code=403)
try:
data = await request.json()
emp_id = UUID(data.get("employee_id"))
emp_user = await db.get_user(emp_id)
if not emp_user or str(emp_user.company_id) != session.company_id:
return JSONResponse({"error": "Employee not found or not in company"}, status_code=403)
perms = await get_employee_permissions_or_default(emp_id, UUID(session.company_id))
if not perms.get("can_manage_post_types", True):
return JSONResponse({"error": "Keine Berechtigung"}, status_code=403)
pt_id = UUID(data.get("post_type_id"))
post_type = await db.get_post_type(pt_id)
if not post_type or post_type.user_id != emp_id:
return JSONResponse({"error": "Post type not found or access denied"}, status_code=404)
post_text = (data.get("post_text") or "").strip()
if len(post_text) < 20:
return JSONResponse({"error": "Post muss mindestens 20 Zeichen lang sein"}, status_code=400)
from uuid import uuid4
from src.database.models import LinkedInPost
manual_post = LinkedInPost(
user_id=emp_id,
post_text=post_text,
post_url=f"manual://{uuid4()}",
post_date=datetime.now(timezone.utc),
post_type_id=pt_id,
classification_method="manual",
classification_confidence=1.0,
raw_data={"source": "manual", "manual_entry": True}
)
saved_posts = await db.save_linkedin_posts([manual_post])
saved = saved_posts[0] if saved_posts else None
return JSONResponse({
"success": True,
"post": {
"id": str(saved.id) if saved and saved.id else None,
"post_text": saved.post_text if saved else post_text,
"post_date": saved.post_date.isoformat() if saved and saved.post_date else None,
}
})
except Exception as e:
logger.error(f"Error adding manual post for company-managed employee post type: {e}")
return JSONResponse({"error": str(e)}, status_code=500)
@user_router.delete("/api/company/manage/post-types/{post_type_id}/posts/{post_id}")
async def company_delete_post_from_employee_type(request: Request, post_type_id: str, post_id: str, employee_id: str):
"""Company deletes a post assigned to an employee's post type."""
session = require_user_session(request)
if not session:
return JSONResponse({"error": "Not authenticated"}, status_code=401)
if session.account_type != "company" or not session.company_id:
return JSONResponse({"error": "Only company accounts can delete posts here"}, status_code=403)
try:
emp_id = UUID(employee_id)
emp_user = await db.get_user(emp_id)
if not emp_user or str(emp_user.company_id) != session.company_id:
return JSONResponse({"error": "Employee not found or not in company"}, status_code=403)
perms = await get_employee_permissions_or_default(emp_id, UUID(session.company_id))
if not perms.get("can_manage_post_types", True):
return JSONResponse({"error": "Keine Berechtigung"}, status_code=403)
pt_id = UUID(post_type_id)
post_type = await db.get_post_type(pt_id)
if not post_type or post_type.user_id != emp_id:
return JSONResponse({"error": "Post type not found or access denied"}, status_code=404)
post = await db.get_linkedin_post(UUID(post_id))
if not post or post.user_id != emp_id or post.post_type_id != pt_id:
return JSONResponse({"error": "Post not found or access denied"}, status_code=404)
await db.delete_linkedin_post(UUID(post_id))
return JSONResponse({"success": True})
except Exception as e:
logger.error(f"Error deleting company-managed employee post from 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."""