Improved features, implemented moco integration

This commit is contained in:
2026-02-18 19:59:14 +01:00
parent af2c9e7fd8
commit 8e4f155a16
11 changed files with 827 additions and 427 deletions

View File

@@ -29,7 +29,7 @@ from src.services.email_service import (
send_approval_request_email,
send_decision_notification_email,
validate_token,
mark_token_used
mark_token_used,
)
from src.services.background_jobs import (
job_manager, JobType, JobStatus,
@@ -1241,36 +1241,41 @@ async def dashboard(request: Request):
if session.company_id:
try:
company = await db.get_company(UUID(session.company_id))
employees_raw = await db.get_company_employees(UUID(session.company_id))
pending_invitations = await db.get_pending_invitations(UUID(session.company_id))
quota = await db.get_company_daily_quota(UUID(session.company_id))
license_key = await db.get_company_limits(UUID(session.company_id))
company_id_uuid = UUID(session.company_id)
company, employees_raw, pending_invitations, quota, license_key = await asyncio.gather(
db.get_company(company_id_uuid),
db.get_company_employees(company_id_uuid),
db.get_pending_invitations(company_id_uuid),
db.get_company_daily_quota(company_id_uuid),
db.get_company_limits(company_id_uuid),
)
except Exception as company_error:
logger.warning(f"Could not load company data for {session.company_id}: {company_error}")
# Continue without company data - better than crashing
# Add avatar URLs to employees
employees = []
for emp in employees_raw:
emp_session = UserSession(
# Add avatar URLs to employees (parallel)
def _make_emp_session(emp):
return UserSession(
user_id=str(emp.id),
linkedin_picture=emp.linkedin_picture,
email=emp.email,
account_type=emp.account_type.value if hasattr(emp.account_type, 'value') else emp.account_type,
display_name=emp.display_name
)
avatar_url = await get_user_avatar(emp_session, emp.id)
# Create employee dict with avatar
emp_dict = {
emp_sessions = [_make_emp_session(emp) for emp in employees_raw]
avatar_urls = await asyncio.gather(*[get_user_avatar(s, emp.id) for s, emp in zip(emp_sessions, employees_raw)])
employees = [
{
"id": emp.id,
"email": emp.email,
"display_name": emp.display_name or emp.linkedin_name or emp.email,
"onboarding_status": emp.onboarding_status,
"avatar_url": avatar_url
}
employees.append(emp_dict)
for emp, avatar_url in zip(employees_raw, avatar_urls)
]
user_id = UUID(session.user_id)
profile_picture = await get_user_avatar(session, user_id)
@@ -1586,14 +1591,13 @@ async def post_detail_page(request: Request, post_id: str):
if str(post.user_id) != session.user_id:
return RedirectResponse(url="/posts", status_code=302)
profile = await db.get_profile(post.user_id)
linkedin_posts = await db.get_linkedin_posts(post.user_id)
profile, linkedin_posts, profile_picture_url, profile_analysis_record = await asyncio.gather(
db.get_profile(post.user_id),
db.get_linkedin_posts(post.user_id),
get_user_avatar(session, post.user_id),
db.get_profile_analysis(post.user_id),
)
reference_posts = [p.post_text for p in linkedin_posts if p.post_text and len(p.post_text) > 100][:10]
# Get avatar with priority: LinkedInAccount > profile_picture > session.linkedin_picture
profile_picture_url = await get_user_avatar(session, post.user_id)
profile_analysis_record = await db.get_profile_analysis(post.user_id)
profile_analysis = profile_analysis_record.full_analysis if profile_analysis_record else None
post_type = None
@@ -2198,7 +2202,7 @@ async def update_post_status(
if status == "approved" and profile and profile.customer_email:
# Build base URL from request
base_url = str(request.base_url).rstrip('/')
email_sent = send_approval_request_email(
email_sent = await send_approval_request_email(
to_email=profile.customer_email,
post_id=UUID(post_id),
post_title=post.topic_title or "Untitled Post",
@@ -2235,6 +2239,9 @@ async def update_post(
if str(post.user_id) != session.user_id:
raise HTTPException(status_code=403, detail="Not authorized")
if len(content) > 3000:
raise HTTPException(status_code=422, detail="Post überschreitet 3000-Zeichen-Limit")
# Save as new version
writer_versions = post.writer_versions or []
writer_versions.append(content)
@@ -2265,7 +2272,7 @@ async def update_post(
@user_router.get("/api/email-action/{token}", response_class=HTMLResponse)
async def handle_email_action(request: Request, token: str):
"""Handle email action (approve/reject) from email link."""
token_data = validate_token(token)
token_data = await validate_token(token)
if not token_data:
return templates.TemplateResponse("email_action_result.html", {
@@ -2306,7 +2313,7 @@ async def handle_email_action(request: Request, token: str):
await db.update_generated_post(post_id, {"status": new_status})
# Mark token as used
mark_token_used(token)
await mark_token_used(token)
# Send notification to creator
if profile and profile.creator_email:
@@ -2713,27 +2720,30 @@ async def company_manage_page(request: Request, employee_id: str = None):
all_employees = await db.get_company_employees(company_id)
active_employees = [emp for emp in all_employees if emp.onboarding_status == "completed"]
# Build display info for employees with correct avatar URLs
# Build display info for employees with correct avatar URLs (parallel)
# Note: emp is a User object from get_company_employees which has linkedin_name and linkedin_picture
active_employees_info = []
for emp in active_employees:
# Create minimal session for employee to get avatar
emp_session = UserSession(
def _make_emp_session_manage(emp):
return UserSession(
user_id=str(emp.id),
linkedin_picture=emp.linkedin_picture,
email=emp.email,
account_type=emp.account_type.value if hasattr(emp.account_type, 'value') else emp.account_type,
display_name=emp.display_name
)
avatar_url = await get_user_avatar(emp_session, emp.id)
active_employees_info.append({
emp_sessions_manage = [_make_emp_session_manage(emp) for emp in active_employees]
emp_avatars = await asyncio.gather(*[get_user_avatar(s, emp.id) for s, emp in zip(emp_sessions_manage, active_employees)])
active_employees_info = [
{
"id": str(emp.id),
"email": emp.email,
"display_name": emp.linkedin_name or emp.display_name or emp.email,
"linkedin_picture": avatar_url, # Now contains the correct avatar with priority
"linkedin_picture": avatar_url,
"onboarding_status": emp.onboarding_status
})
}
for emp, avatar_url in zip(active_employees, emp_avatars)
]
# Selected employee data
selected_employee = None
@@ -2749,10 +2759,11 @@ async def company_manage_page(request: Request, employee_id: str = None):
break
if selected_employee:
# Get employee's user_id
emp_profile = await db.get_profile(UUID(employee_id))
emp_profile, employee_posts = await asyncio.gather(
db.get_profile(UUID(employee_id)),
db.get_generated_posts(UUID(employee_id)),
)
if emp_profile:
employee_posts = await db.get_generated_posts(emp_profile.id)
pending_posts = len([p for p in employee_posts if p.status in ['draft', 'pending']])
approved_posts = len([p for p in employee_posts if p.status in ['approved', 'published']])
@@ -2827,24 +2838,25 @@ async def company_manage_post_detail(request: Request, post_id: str, employee_id
if not employee_id:
return RedirectResponse(url="/company/manage", status_code=302)
# Get employee info
emp_profile = await db.get_profile(UUID(employee_id))
# Get employee info + post in parallel
emp_profile, emp_user, post = await asyncio.gather(
db.get_profile(UUID(employee_id)),
db.get_user(UUID(employee_id)),
db.get_generated_post(UUID(post_id)),
)
if not emp_profile:
return RedirectResponse(url="/company/manage", status_code=302)
# Verify employee belongs to this company
emp_user = await db.get_user(UUID(employee_id))
if not emp_user or str(emp_user.company_id) != session.company_id:
return RedirectResponse(url="/company/manage", status_code=302)
post = await db.get_generated_post(UUID(post_id))
if not post or str(post.user_id) != str(emp_profile.id):
return RedirectResponse(url=f"/company/manage/posts?employee_id={employee_id}", status_code=302)
profile = await db.get_profile(emp_profile.id)
profile = emp_profile # same object - no second DB call needed
# Get employee's avatar
# Note: Create minimal session for the employee to use get_user_avatar
emp_session = UserSession(
user_id=str(emp_profile.id),
linkedin_picture=emp_user.linkedin_picture,