from __future__ import annotations from datetime import date, datetime from typing import Any from app.models import ( Announcement, Event, FileAsset, Group, HomeDevice, HomeProfile, Member, MemberDevice, Message, Poll, PollOption, PollVote, RemoteServerConnection, Task, Thread, ) def iso(value: datetime | date | None) -> str | None: if value is None: return None return value.isoformat() def group_dict(group: Group) -> dict[str, Any]: return { "id": group.id, "server_origin": group.server_origin, "name": group.name, "description": group.description, "visibility": group.visibility, "legacy_channel_status": group.legacy_channel_status, "transition_deadline": iso(group.transition_deadline), "created_at": iso(group.created_at), "updated_at": iso(group.updated_at), } def member_dict(member: Member) -> dict[str, Any]: return { "id": member.id, "group_id": member.group_id, "home_profile_id": member.home_profile_id, "display_name": member.display_name, "role": member.role, "status": member.status, "joined_at": iso(member.joined_at), "last_seen_at": iso(member.last_seen_at), "notification_enabled_at": iso(member.notification_enabled_at), } def profile_dict(profile: HomeProfile | None, member: Member | None = None) -> dict[str, Any] | None: if profile: return { "id": profile.id, "primary_display_name": profile.primary_display_name, "status": profile.status, "last_seen_at": iso(profile.last_seen_at), } if member: return { "id": None, "primary_display_name": member.display_name, "status": "membership_only", "last_seen_at": iso(member.last_seen_at), } return None def announcement_dict(announcement: Announcement, group: Group | None = None) -> dict[str, Any]: return { "id": announcement.id, "group_id": announcement.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "author_member_id": announcement.author_member_id, "title": announcement.title, "body": announcement.body, "priority": announcement.priority, "official": announcement.official, "requires_ack": announcement.requires_ack, "created_at": iso(announcement.created_at), "updated_at": iso(announcement.updated_at), } def event_dict(event: Event, group: Group | None = None, rsvp_status: str | None = None) -> dict[str, Any]: return { "id": event.id, "group_id": event.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "created_by_member_id": event.created_by_member_id, "title": event.title, "description": event.description, "starts_at": iso(event.starts_at), "ends_at": iso(event.ends_at), "location_name": event.location_name, "location_address": event.location_address, "rsvp_required": event.rsvp_required, "rsvp_status": rsvp_status, "changed_at": iso(event.changed_at), "created_at": iso(event.created_at), "updated_at": iso(event.updated_at), } def task_dict(task: Task, group: Group | None = None) -> dict[str, Any]: return { "id": task.id, "group_id": task.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "created_by_member_id": task.created_by_member_id, "assigned_to_member_id": task.assigned_to_member_id, "title": task.title, "description": task.description, "due_at": iso(task.due_at), "status": task.status, "created_at": iso(task.created_at), "updated_at": iso(task.updated_at), } def poll_dict(poll: Poll, options: list[PollOption], votes: list[PollVote], group: Group | None = None) -> dict[str, Any]: counts: dict[str, int] = {option.id: 0 for option in options} for vote in votes: counts[vote.option_id] = counts.get(vote.option_id, 0) + 1 return { "id": poll.id, "group_id": poll.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "title": poll.title, "description": poll.description, "closes_at": iso(poll.closes_at), "status": poll.status, "created_by_member_id": poll.created_by_member_id, "created_at": iso(poll.created_at), "options": [{"id": option.id, "label": option.label, "position": option.position, "vote_count": counts.get(option.id, 0)} for option in options], } def file_dict(file: FileAsset, group: Group | None = None) -> dict[str, Any]: return { "id": file.id, "group_id": file.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "uploaded_by_member_id": file.uploaded_by_member_id, "filename_original": file.filename_original, "content_type": file.content_type, "size_bytes": file.size_bytes, "visibility": file.visibility, "description": file.description, "requires_ack": file.requires_ack, "created_at": iso(file.created_at), "download_url": f"/api/files/{file.id}/download", } def thread_dict(thread: Thread, messages: list[Message] | None = None, group: Group | None = None) -> dict[str, Any]: return { "id": thread.id, "group_id": thread.group_id, "group_name": group.name if group else None, "source_type": "local", "source_server_origin": group.server_origin if group else None, "title": thread.title, "kind": thread.kind, "created_by_member_id": thread.created_by_member_id, "created_at": iso(thread.created_at), "updated_at": iso(thread.updated_at), "messages": [message_dict(message) for message in messages or []], } def message_dict(message: Message) -> dict[str, Any]: return { "id": message.id, "thread_id": message.thread_id, "author_member_id": message.author_member_id, "body": message.body, "created_at": iso(message.created_at), "edited_at": iso(message.edited_at), "deleted_at": iso(message.deleted_at), } def action_dict( *, id: str, source_type: str, source_server_origin: str, source_group_id: str, source_group_name: str, type: str, priority: str, title: str, summary: str, object_type: str, object_id: str, due_at: datetime | None = None, status: str = "open", ) -> dict[str, Any]: return { "id": id, "source_type": source_type, "source_server_origin": source_server_origin, "source_group_id": source_group_id, "source_group_name": source_group_name, "type": type, "status": status, "priority": priority, "title": title, "summary": summary, "object_type": object_type, "object_id": object_id, "due_at": iso(due_at), } def device_dict(device: HomeDevice | MemberDevice, current_id: str | None = None) -> dict[str, Any]: return { "id": device.id, "label": device.label, "created_at": iso(device.created_at), "last_seen_at": iso(device.last_seen_at), "revoked_at": iso(device.revoked_at), "trust_level": device.trust_level, "current": device.id == current_id, } def remote_connection_dict(connection: RemoteServerConnection) -> dict[str, Any]: return { "id": connection.id, "server_origin": connection.server_origin, "server_name": connection.server_name, "api_base": connection.api_base, "protocol_version": connection.protocol_version, "capabilities": connection.capabilities_json, "status": connection.status, "last_sync_at": iso(connection.last_sync_at), "last_error": connection.last_error, "created_at": iso(connection.created_at), }