diff --git a/src/tracker/app.py b/src/tracker/app.py index 9dfceb5..e8d8c52 100644 --- a/src/tracker/app.py +++ b/src/tracker/app.py @@ -64,6 +64,15 @@ async def lifespan(app: FastAPI): await conn.run_sync(Base.metadata.create_all) logger.info("Database tables ensured.") + # Auto-seed if DB is empty (dev only) + async with async_session() as session: + result = await session.execute(select(Member).limit(1)) + if not result.scalar_one_or_none(): + logger.info("Empty DB detected in dev mode — running seed...") + from tracker.init_db import seed_dev_data + await seed_dev_data(session) + logger.info("Dev seed complete.") + heartbeat_task = asyncio.create_task(heartbeat_monitor()) yield heartbeat_task.cancel() diff --git a/src/tracker/init_db.py b/src/tracker/init_db.py index 0ca4726..70004cc 100644 --- a/src/tracker/init_db.py +++ b/src/tracker/init_db.py @@ -1,11 +1,11 @@ -"""Initialize database — drop all and create fresh tables + seed data.""" +"""Initialize database — dev seed data.""" import asyncio import hashlib import logging -import uuid -from tracker.database import engine, async_session +from sqlalchemy.ext.asyncio import AsyncSession + from tracker.enums import AuthMethod, ChatKind, MemberRole, MemberStatus, MemberType, ProjectStatus from tracker.models import Base, Member, Chat, Project, ProjectMember, AgentConfig @@ -16,85 +16,72 @@ def hash_password(password: str) -> str: return hashlib.sha256(password.encode()).hexdigest() -async def init_db(): - """Drop all tables and recreate from models. Seed with admin + lobby.""" +async def seed_dev_data(session: AsyncSession): + """Seed dev data into an existing session. Expects empty DB.""" + # Admin (owner) + admin = Member( + name="Admin", + slug="admin", + type=MemberType.HUMAN, + role=MemberRole.OWNER, + auth_method=AuthMethod.PASSWORD, + password_hash=hash_password("teamboard"), + status=MemberStatus.OFFLINE, + ) + session.add(admin) + + # Coder agent (for dev/testing) + coder = Member( + name="Coder", + slug="coder", + type=MemberType.AGENT, + role=MemberRole.MEMBER, + auth_method=AuthMethod.TOKEN, + token="tb-coder-dev-token", + status=MemberStatus.OFFLINE, + ) + session.add(coder) + await session.flush() + + # Team-board project + project = Project( + name="Team Board", + slug="team-board", + description="AI agent collaboration platform", + status=ProjectStatus.ACTIVE, + ) + session.add(project) + await session.flush() + + # Project chat + project_chat = Chat(kind=ChatKind.PROJECT, project_id=project.id) + session.add(project_chat) + + # Project members + session.add(ProjectMember(project_id=project.id, member_id=admin.id, role=MemberRole.OWNER)) + session.add(ProjectMember(project_id=project.id, member_id=coder.id, role=MemberRole.MEMBER)) + + # Lobby chat + lobby = Chat(kind=ChatKind.LOBBY, project_id=None) + session.add(lobby) + + await session.commit() + logger.info("Dev seed: admin, coder, project team-board (id=%s), lobby", project.id) + + +async def reset_db(): + """Drop all tables, recreate, and seed. For dev use only.""" + from tracker.database import engine, async_session + async with engine.begin() as conn: - logger.info("Dropping all tables...") await conn.run_sync(Base.metadata.drop_all) - logger.info("Creating all tables...") await conn.run_sync(Base.metadata.create_all) + logger.info("Tables recreated.") async with async_session() as session: - # Create admin (owner) - admin = Member( - name="Admin", - slug="admin", - type=MemberType.HUMAN, - role=MemberRole.OWNER, - auth_method=AuthMethod.PASSWORD, - password_hash=hash_password("teamboard"), - status=MemberStatus.OFFLINE, - ) - session.add(admin) - - # Create coder agent - coder = Member( - name="Coder", - slug="coder", - type=MemberType.AGENT, - role=MemberRole.MEMBER, - auth_method=AuthMethod.TOKEN, - token="tb-coder-dev-token", - status=MemberStatus.OFFLINE, - ) - session.add(coder) - - # Flush to get member IDs - await session.flush() - - # Create team-board project - project = Project( - name="Team Board", - slug="team-board", - description="AI agent collaboration platform", - status=ProjectStatus.ACTIVE, - ) - session.add(project) - await session.flush() - - # Create project chat - project_chat = Chat( - kind=ChatKind.PROJECT, - project_id=project.id, - ) - session.add(project_chat) - - # Create project members - admin_member = ProjectMember( - project_id=project.id, - member_id=admin.id, - role=MemberRole.OWNER, - ) - session.add(admin_member) - - coder_member = ProjectMember( - project_id=project.id, - member_id=coder.id, - role=MemberRole.MEMBER, - ) - session.add(coder_member) - - # Create lobby chat - lobby = Chat( - kind=ChatKind.LOBBY, - project_id=None, - ) - session.add(lobby) - - await session.commit() - logger.info("Seed data created: admin user, coder agent, team-board project (id=%s), lobby chat (id=%s)", project.id, lobby.id) + await seed_dev_data(session) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - asyncio.run(init_db()) + asyncio.run(reset_db())