Files
comiaunicaty/backend/app/db/seed.py

321 lines
12 KiB
Python

from __future__ import annotations
from datetime import timedelta
from sqlalchemy import select
from app.core.config import get_settings
from app.core.security import hash_token, token_urlsafe, utc_now
import app.db.base as db_base
from app.db.base import Base, init_db
from app.models import (
Announcement,
ConnectionToken,
Event,
FileAsset,
Group,
HomeProfile,
Member,
MemberInvite,
Message,
MigrationState,
Notification,
NotificationPreference,
Poll,
PollOption,
Task,
Thread,
)
DEMO_INVITE_TOKEN = "demo-fc-invite"
DEMO_REMOTE_CONNECTION_CODE = "demo-remote-sync-code"
def _add_member(db, group: Group, name: str, role: str = "member", profile: HomeProfile | None = None, status: str = "joined") -> Member:
member = Member(
group_id=group.id,
home_profile_id=profile.id if profile else None,
display_name=name,
role=role,
status=status,
joined_at=utc_now() - timedelta(days=9) if status in {"joined", "verified"} else None,
last_seen_at=utc_now() - timedelta(days=5),
notification_enabled_at=utc_now() - timedelta(days=3) if name == "Anna Müller" else None,
)
db.add(member)
db.flush()
return member
def _add_file(db, group: Group, member: Member, filename: str, description: str, requires_ack: bool = False) -> None:
db.add(
FileAsset(
group_id=group.id,
uploaded_by_member_id=member.id,
filename_original=filename,
filename_stored=f"seed-{filename.replace(' ', '_')}",
content_type="text/plain",
size_bytes=len(description.encode("utf-8")),
storage_path=f"seed://{filename}",
description=description,
requires_ack=requires_ack,
)
)
def seed() -> None:
settings = get_settings()
Base.metadata.drop_all(bind=db_base.engine)
init_db()
db = db_base.SessionLocal()
try:
anna = HomeProfile(primary_display_name="Anna Müller", last_seen_at=utc_now() - timedelta(days=5))
db.add(anna)
db.flush()
groups = [
Group(
server_origin=settings.server_origin,
name="FC Kreuzberg U12 Parents",
description="Planning, matches, drivers, files, and official team announcements.",
visibility="private",
legacy_channel_status="transition",
transition_deadline=(utc_now() + timedelta(days=21)).date(),
),
Group(
server_origin=settings.server_origin,
name="Class 4B Parents",
description="School forms, parent evenings, votes, and classroom coordination.",
visibility="private",
legacy_channel_status="transition",
transition_deadline=(utc_now() + timedelta(days=28)).date(),
),
Group(
server_origin=settings.server_origin,
name="Tenant Association",
description="Building updates, maintenance actions, meeting minutes, and votes.",
visibility="private",
legacy_channel_status="transition",
transition_deadline=(utc_now() + timedelta(days=14)).date(),
),
Group(
server_origin=settings.server_origin,
name="Food Bank Volunteers",
description="Volunteer shifts, supply lists, files, and announcements.",
visibility="private",
legacy_channel_status="transition",
transition_deadline=(utc_now() + timedelta(days=30)).date(),
),
]
db.add_all(groups)
db.flush()
fc, school, tenants, food = groups
anna_fc = _add_member(db, fc, "Anna Müller", "admin", anna)
coach = _add_member(db, fc, "Coach Mark", "owner")
lisa_fc = _add_member(db, fc, "Lisa Becker")
samir_fc = _add_member(db, fc, "Samir Khan")
_add_member(db, fc, "Priya N.", status="invited")
anna_school = _add_member(db, school, "Anna Müller", "member", anna)
lisa_school = _add_member(db, school, "Lisa Becker", "admin")
samir_school = _add_member(db, school, "Samir Khan")
anna_tenant = _add_member(db, tenants, "Anna Müller", "member", anna)
tenant_admin = _add_member(db, tenants, "Tenant admin", "owner")
_add_member(db, tenants, "Priya N.", "moderator")
anna_food = _add_member(db, food, "Anna Müller", "member", anna)
priya_food = _add_member(db, food, "Priya N.", "admin")
_add_member(db, food, "Samir Khan")
db.add(
MemberInvite(
group_id=fc.id,
created_by_member_id=anna_fc.id,
label="Parent open invite",
scope="open_seat",
permission_role="member",
token_hash=hash_token(DEMO_INVITE_TOKEN),
max_uses=100,
)
)
now = utc_now()
db.add_all(
[
Announcement(
group_id=fc.id,
author_member_id=coach.id,
title="Official move: match details live here",
body="Schedules, RSVP, driver planning, and files are now handled in GroupHome.",
priority="urgent",
official=True,
requires_ack=True,
),
Announcement(
group_id=school.id,
author_member_id=lisa_school.id,
title="School form due Friday",
body="Please download the excursion form and return it by Friday morning.",
official=True,
requires_ack=False,
),
Announcement(
group_id=food.id,
author_member_id=priya_food.id,
title="Saturday shift checklist",
body="Bring gloves if you have them. New shelf labels are attached in files.",
official=True,
),
]
)
db.add_all(
[
Event(
group_id=fc.id,
created_by_member_id=coach.id,
title="League match vs. Neukölln",
description="Please RSVP and add a note if you can drive.",
starts_at=now + timedelta(days=2, hours=3),
ends_at=now + timedelta(days=2, hours=5),
location_name="Willi-Boos-Sportplatz",
location_address="Gneisenaustr. 36, Berlin",
rsvp_required=True,
),
Event(
group_id=fc.id,
created_by_member_id=coach.id,
title="Training moved to Pitch 2",
description="The usual pitch is closed for maintenance.",
starts_at=now + timedelta(days=1, hours=2),
ends_at=now + timedelta(days=1, hours=4),
location_name="Pitch 2",
rsvp_required=False,
changed_at=now - timedelta(days=1),
),
Event(
group_id=school.id,
created_by_member_id=lisa_school.id,
title="Parent evening",
description="Agenda: class trip, reading groups, and summer project.",
starts_at=now + timedelta(days=5, hours=1),
location_name="Classroom 4B",
rsvp_required=True,
),
Event(
group_id=food.id,
created_by_member_id=priya_food.id,
title="Volunteer shift",
description="Sorting and front desk support.",
starts_at=now + timedelta(days=3, hours=4),
location_name="Food Bank Hall",
rsvp_required=True,
),
]
)
db.add_all(
[
Task(
group_id=fc.id,
created_by_member_id=coach.id,
assigned_to_member_id=anna_fc.id,
title="Confirm one more driver",
description="We need one extra car for Saturday.",
due_at=now + timedelta(days=1),
),
Task(
group_id=food.id,
created_by_member_id=priya_food.id,
assigned_to_member_id=anna_food.id,
title="Bring label printer",
description="Use it for shelf relabeling before the morning shift.",
due_at=now + timedelta(days=2),
),
]
)
poll = Poll(
group_id=tenants.id,
created_by_member_id=tenant_admin.id,
title="Vote: courtyard repair appointment",
description="Choose the appointment that works for your household.",
closes_at=now + timedelta(days=4),
status="open",
)
db.add(poll)
db.flush()
db.add_all(
[
PollOption(poll_id=poll.id, label="Tuesday morning", position=1),
PollOption(poll_id=poll.id, label="Thursday afternoon", position=2),
PollOption(poll_id=poll.id, label="Either is fine", position=3),
]
)
_add_file(db, fc, coach, "Season schedule.txt", "Full U12 season schedule and match locations.", True)
_add_file(db, fc, coach, "Emergency contacts.txt", "Emergency contacts and consent notes.", False)
_add_file(db, tenants, tenant_admin, "Meeting minutes.txt", "Tenant association meeting notes.", False)
_add_file(db, school, lisa_school, "School excursion form.txt", "Please print and return this form.", True)
for group, creator, title, body in [
(fc, lisa_fc, "Snack coordination", "I can bring oranges. Who can bring water?"),
(fc, samir_fc, "Carpool planning", "I have two seats from Kottbusser Tor."),
(tenants, tenant_admin, "Maintenance issue", "Elevator inspection is scheduled next week."),
(food, priya_food, "Volunteer supplies", "We are low on tape and shelf labels."),
]:
thread = Thread(group_id=group.id, created_by_member_id=creator.id, title=title, kind="discussion")
db.add(thread)
db.flush()
db.add(Message(thread_id=thread.id, author_member_id=creator.id, body=body))
db.add(
Notification(
home_profile_id=anna.id,
member_id=anna_fc.id,
title="Coach Mark mentioned you",
body="Can you confirm the last driver for Saturday?",
category="direct_mentions",
)
)
for owner_member in [anna_fc, anna_school, anna_tenant, anna_food]:
for category, delivery in [
("direct_mentions", "immediate"),
("event_changes", "immediate"),
("urgent_announcements", "immediate"),
("tasks_assigned", "immediate"),
("discussions", "digest"),
("new_files", "digest"),
("general_chatter", "digest"),
("reactions", "muted"),
("off_topic", "muted"),
]:
db.add(NotificationPreference(home_profile_id=anna.id, member_id=owner_member.id, category=category, delivery=delivery))
for group in groups:
db.add(MigrationState(group_id=group.id))
db.add(
ConnectionToken(
created_by_member_id=anna_fc.id,
label="Demo remote sync code",
token_hash=hash_token(DEMO_REMOTE_CONNECTION_CODE),
scopes_json=["sync:read"],
)
)
db.commit()
print("Seed complete.")
print(f"Demo invite URL: {settings.frontend_origin}/join/{DEMO_INVITE_TOKEN}")
print(f"Demo remote connection code: {DEMO_REMOTE_CONNECTION_CODE}")
finally:
db.close()
if __name__ == "__main__":
seed()