diff --git a/src/database/client.py b/src/database/client.py index fe4ba83..2a4b5f9 100644 --- a/src/database/client.py +++ b/src/database/client.py @@ -1640,6 +1640,45 @@ class DatabaseClient: ) return [LinkedInAccount(**row) for row in result.data] + # ==================== EMPLOYEE COMPANY PERMISSIONS ==================== + + async def get_employee_permissions(self, user_id: UUID, company_id: UUID): + """Get permissions for an employee in a company. Returns None if no row exists (treat as all-true defaults).""" + from src.database.models import EmployeeCompanyPermissions + result = await asyncio.to_thread( + lambda: self.client.table("employee_company_permissions").select("*").eq( + "user_id", str(user_id) + ).eq("company_id", str(company_id)).execute() + ) + if result.data: + return EmployeeCompanyPermissions(**result.data[0]) + return None + + async def upsert_employee_permissions(self, user_id: UUID, company_id: UUID, updates: Dict[str, Any]) -> None: + """Insert or update employee-company permissions.""" + data = { + "user_id": str(user_id), + "company_id": str(company_id), + **updates + } + await asyncio.to_thread( + lambda: self.client.table("employee_company_permissions").upsert( + data, on_conflict="user_id,company_id" + ).execute() + ) + logger.info(f"Upserted permissions for user {user_id} in company {company_id}") + + async def get_scheduled_posts_for_user(self, user_id: UUID) -> List[GeneratedPost]: + """Get scheduled/approved/published posts for an employee (for their calendar).""" + result = await asyncio.to_thread( + lambda: self.client.table("generated_posts").select("*").eq( + "user_id", str(user_id) + ).in_( + "status", ["approved", "ready", "scheduled", "published"] + ).order("scheduled_at", desc=False, nullsfirst=False).execute() + ) + return [GeneratedPost(**item) for item in result.data] + # Global database client instance db = DatabaseClient() diff --git a/src/database/models.py b/src/database/models.py index b3f58bb..b74faad 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -415,3 +415,19 @@ class CompanyDailyQuota(DBModel): tokens_used: int = 0 created_at: Optional[datetime] = None updated_at: Optional[datetime] = None + + +class EmployeeCompanyPermissions(DBModel): + """Permissions granted by an employee to their company.""" + id: Optional[UUID] = None + user_id: UUID + company_id: UUID + can_create_posts: bool = True + can_view_posts: bool = True + can_edit_posts: bool = True + can_schedule_posts: bool = True + can_manage_post_types: bool = True + can_do_research: bool = True + can_see_in_calendar: bool = True + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None diff --git a/src/services/email_service.py b/src/services/email_service.py index 72da2ec..7958aeb 100644 --- a/src/services/email_service.py +++ b/src/services/email_service.py @@ -442,6 +442,126 @@ def send_employee_removal_email( ) +def send_company_approval_request_email( + to_email: str, + post_title: str, + post_content: str, + company_name: str, + approve_url: str, + reject_url: str, + image_url: Optional[str] = None +) -> bool: + """Send email to employee asking them to approve or reject a company-reviewed post.""" + html_content = f""" + + + + + + + +
+
+

Dein Post wartet auf deine Freigabe

+
+
+

Hallo,

+

Das Unternehmen {company_name} hat deinen Post bearbeitet und bittet um deine Freigabe:

+ +

{post_title}

+ +
{post_content}
+ + {f'Post-Bild' if image_url else ''} + +

Bitte entscheide, ob der Post so veröffentlicht werden soll:

+ + +
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Freigabe erforderlich: {post_title}", + html_content=html_content + ) + + +def send_company_review_notification_email( + to_email: str, + post_id: UUID, + post_title: str, + company_name: str, + base_url: str +) -> bool: + """Send email to employee notifying them the company has reviewed their post.""" + post_url = f"{base_url}/posts/{post_id}" + + html_content = f""" + + + + + + + +
+
+

Dein Post wurde bearbeitet

+
+
+

Hallo,

+

Das Unternehmen {company_name} hat deinen Post in Bearbeitung genommen:

+

{post_title}

+

Der Post wurde in die Spalte "Bearbeitet" verschoben. Du kannst ihn in deinem Dashboard einsehen.

+ Post ansehen +
+ +
+ + + """ + + return send_email( + to_email=to_email, + subject=f"Dein Post wurde bearbeitet: {post_title}", + html_content=html_content + ) + + def send_decision_notification_email( to_email: str, post_title: str, diff --git a/src/web/templates/user/base.html b/src/web/templates/user/base.html index 8267506..f067e2e 100644 --- a/src/web/templates/user/base.html +++ b/src/web/templates/user/base.html @@ -39,6 +39,13 @@ main.sidebar-collapsed { margin-left: 4rem; } .toggle-btn { cursor: pointer; transition: transform 0.3s ease; } aside.collapsed .toggle-btn { transform: rotate(180deg); } + /* Hide nav scrollbar when sidebar collapsed */ + aside.collapsed nav { overflow-y: hidden; overflow-x: hidden; } + /* Subtle scrollbar inside nav */ + nav::-webkit-scrollbar { width: 4px; } + nav::-webkit-scrollbar-track { background: transparent; } + nav::-webkit-scrollbar-thumb { background: #4a5858; border-radius: 4px; } + nav::-webkit-scrollbar-thumb:hover { background: #5a6868; } @@ -110,7 +117,7 @@ {% endif %} -