321 lines
12 KiB
Python
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()
|