forked from cardosofelipe/fast-next-template
refactor(init_db): remove demo data file and implement structured seeding
- Delete `demo_data.json` replaced by structured logic for better modularity. - Add support for seeding default agent types and new demo data structure. - Ensure demo mode only executes when explicitly enabled (settings.DEMO_MODE). - Enhance logging for improved debugging during DB initialization.
This commit is contained in:
@@ -1,366 +0,0 @@
|
||||
{
|
||||
"organizations": [
|
||||
{
|
||||
"name": "Acme Corp",
|
||||
"slug": "acme-corp",
|
||||
"description": "A leading provider of coyote-catching equipment."
|
||||
},
|
||||
{
|
||||
"name": "Globex Corporation",
|
||||
"slug": "globex",
|
||||
"description": "We own the East Coast."
|
||||
},
|
||||
{
|
||||
"name": "Soylent Corp",
|
||||
"slug": "soylent",
|
||||
"description": "Making food for the future."
|
||||
},
|
||||
{
|
||||
"name": "Initech",
|
||||
"slug": "initech",
|
||||
"description": "Software for the soul."
|
||||
},
|
||||
{
|
||||
"name": "Umbrella Corporation",
|
||||
"slug": "umbrella",
|
||||
"description": "Our business is life itself."
|
||||
},
|
||||
{
|
||||
"name": "Massive Dynamic",
|
||||
"slug": "massive-dynamic",
|
||||
"description": "What don't we do?"
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"email": "demo@example.com",
|
||||
"password": "DemoPass1234!",
|
||||
"first_name": "Demo",
|
||||
"last_name": "User",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "alice@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Alice",
|
||||
"last_name": "Smith",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "bob@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Bob",
|
||||
"last_name": "Jones",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "charlie@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Charlie",
|
||||
"last_name": "Brown",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "diana@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Diana",
|
||||
"last_name": "Prince",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "carol@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Carol",
|
||||
"last_name": "Williams",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "owner",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "dan@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Dan",
|
||||
"last_name": "Miller",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "ellen@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Ellen",
|
||||
"last_name": "Ripley",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "fred@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Fred",
|
||||
"last_name": "Flintstone",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "dave@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Dave",
|
||||
"last_name": "Brown",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "gina@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Gina",
|
||||
"last_name": "Torres",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "harry@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Harry",
|
||||
"last_name": "Potter",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "eve@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Eve",
|
||||
"last_name": "Davis",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "iris@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Iris",
|
||||
"last_name": "West",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "jack@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Jack",
|
||||
"last_name": "Sparrow",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "frank@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Frank",
|
||||
"last_name": "Miller",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "george@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "George",
|
||||
"last_name": "Costanza",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "kate@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Kate",
|
||||
"last_name": "Bishop",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "leo@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Leo",
|
||||
"last_name": "Messi",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "owner",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "mary@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Mary",
|
||||
"last_name": "Jane",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "nathan@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Nathan",
|
||||
"last_name": "Drake",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "olivia@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Olivia",
|
||||
"last_name": "Dunham",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "peter@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Peter",
|
||||
"last_name": "Parker",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "quinn@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Quinn",
|
||||
"last_name": "Mallory",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "grace@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Grace",
|
||||
"last_name": "Hopper",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "heidi@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Heidi",
|
||||
"last_name": "Klum",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "ivan@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Ivan",
|
||||
"last_name": "Drago",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "rachel@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Rachel",
|
||||
"last_name": "Green",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "sam@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Sam",
|
||||
"last_name": "Wilson",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "tony@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "una@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Una",
|
||||
"last_name": "Chin-Riley",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "victor@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Victor",
|
||||
"last_name": "Von Doom",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "wanda@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Wanda",
|
||||
"last_name": "Maximoff",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,27 +3,48 @@
|
||||
Async database initialization script.
|
||||
|
||||
Creates the first superuser if configured and doesn't already exist.
|
||||
Seeds default agent types (production data) and demo data (when DEMO_MODE is enabled).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import select, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal, engine
|
||||
from app.crud.syndarix.agent_type import agent_type as agent_type_crud
|
||||
from app.crud.user import user as user_crud
|
||||
from app.models.organization import Organization
|
||||
from app.models.syndarix import AgentInstance, AgentType, Issue, Project, Sprint
|
||||
from app.models.syndarix.enums import (
|
||||
AgentStatus,
|
||||
AutonomyLevel,
|
||||
ClientMode,
|
||||
IssuePriority,
|
||||
IssueStatus,
|
||||
IssueType,
|
||||
ProjectComplexity,
|
||||
ProjectStatus,
|
||||
SprintStatus,
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.models.user_organization import UserOrganization
|
||||
from app.schemas.syndarix import AgentTypeCreate
|
||||
from app.schemas.users import UserCreate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Data file paths
|
||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
DEFAULT_AGENT_TYPES_PATH = DATA_DIR / "default_agent_types.json"
|
||||
DEMO_DATA_PATH = DATA_DIR / "demo_data.json"
|
||||
|
||||
|
||||
async def init_db() -> User | None:
|
||||
"""
|
||||
@@ -54,28 +75,29 @@ async def init_db() -> User | None:
|
||||
|
||||
if existing_user:
|
||||
logger.info(f"Superuser already exists: {existing_user.email}")
|
||||
return existing_user
|
||||
else:
|
||||
# Create superuser if doesn't exist
|
||||
user_in = UserCreate(
|
||||
email=superuser_email,
|
||||
password=superuser_password,
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
is_superuser=True,
|
||||
)
|
||||
|
||||
# Create superuser if doesn't exist
|
||||
user_in = UserCreate(
|
||||
email=superuser_email,
|
||||
password=superuser_password,
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
is_superuser=True,
|
||||
)
|
||||
existing_user = await user_crud.create(session, obj_in=user_in)
|
||||
await session.commit()
|
||||
await session.refresh(existing_user)
|
||||
logger.info(f"Created first superuser: {existing_user.email}")
|
||||
|
||||
user = await user_crud.create(session, obj_in=user_in)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
# ALWAYS load default agent types (production data)
|
||||
await load_default_agent_types(session)
|
||||
|
||||
logger.info(f"Created first superuser: {user.email}")
|
||||
|
||||
# Create demo data if in demo mode
|
||||
# Only load demo data if in demo mode
|
||||
if settings.DEMO_MODE:
|
||||
await load_demo_data(session)
|
||||
|
||||
return user
|
||||
return existing_user
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
@@ -88,26 +110,89 @@ def _load_json_file(path: Path):
|
||||
return json.load(f)
|
||||
|
||||
|
||||
async def load_demo_data(session):
|
||||
"""Load demo data from JSON file."""
|
||||
demo_data_path = Path(__file__).parent / "core" / "demo_data.json"
|
||||
if not demo_data_path.exists():
|
||||
logger.warning(f"Demo data file not found: {demo_data_path}")
|
||||
async def load_default_agent_types(session: AsyncSession) -> None:
|
||||
"""
|
||||
Load default agent types from JSON file.
|
||||
|
||||
These are production defaults - created only if they don't exist, never overwritten.
|
||||
This allows users to customize agent types without worrying about server restarts.
|
||||
"""
|
||||
if not DEFAULT_AGENT_TYPES_PATH.exists():
|
||||
logger.warning(
|
||||
f"Default agent types file not found: {DEFAULT_AGENT_TYPES_PATH}"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
# Use asyncio.to_thread to avoid blocking the event loop
|
||||
data = await asyncio.to_thread(_load_json_file, demo_data_path)
|
||||
data = await asyncio.to_thread(_load_json_file, DEFAULT_AGENT_TYPES_PATH)
|
||||
|
||||
# Create Organizations
|
||||
org_map = {}
|
||||
for org_data in data.get("organizations", []):
|
||||
# Check if org exists
|
||||
result = await session.execute(
|
||||
text("SELECT * FROM organizations WHERE slug = :slug"),
|
||||
{"slug": org_data["slug"]},
|
||||
for agent_type_data in data:
|
||||
slug = agent_type_data["slug"]
|
||||
|
||||
# Check if agent type already exists
|
||||
existing = await agent_type_crud.get_by_slug(session, slug=slug)
|
||||
|
||||
if existing:
|
||||
logger.debug(f"Agent type already exists: {agent_type_data['name']}")
|
||||
continue
|
||||
|
||||
# Create the agent type
|
||||
agent_type_in = AgentTypeCreate(
|
||||
name=agent_type_data["name"],
|
||||
slug=slug,
|
||||
description=agent_type_data.get("description"),
|
||||
expertise=agent_type_data.get("expertise", []),
|
||||
personality_prompt=agent_type_data["personality_prompt"],
|
||||
primary_model=agent_type_data["primary_model"],
|
||||
fallback_models=agent_type_data.get("fallback_models", []),
|
||||
model_params=agent_type_data.get("model_params", {}),
|
||||
mcp_servers=agent_type_data.get("mcp_servers", []),
|
||||
tool_permissions=agent_type_data.get("tool_permissions", {}),
|
||||
is_active=agent_type_data.get("is_active", True),
|
||||
)
|
||||
existing_org = result.first()
|
||||
|
||||
await agent_type_crud.create(session, obj_in=agent_type_in)
|
||||
logger.info(f"Created default agent type: {agent_type_data['name']}")
|
||||
|
||||
logger.info("Default agent types loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading default agent types: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def load_demo_data(session: AsyncSession) -> None:
|
||||
"""
|
||||
Load demo data from JSON file.
|
||||
|
||||
Only runs when DEMO_MODE is enabled. Creates demo organizations, users,
|
||||
projects, sprints, agent instances, and issues.
|
||||
"""
|
||||
if not DEMO_DATA_PATH.exists():
|
||||
logger.warning(f"Demo data file not found: {DEMO_DATA_PATH}")
|
||||
return
|
||||
|
||||
try:
|
||||
data = await asyncio.to_thread(_load_json_file, DEMO_DATA_PATH)
|
||||
|
||||
# Build lookup maps for FK resolution
|
||||
org_map: dict[str, Organization] = {}
|
||||
user_map: dict[str, User] = {}
|
||||
project_map: dict[str, Project] = {}
|
||||
sprint_map: dict[str, Sprint] = {} # key: "project_slug:sprint_number"
|
||||
agent_type_map: dict[str, AgentType] = {}
|
||||
agent_instance_map: dict[
|
||||
str, AgentInstance
|
||||
] = {} # key: "project_slug:agent_name"
|
||||
|
||||
# ========================
|
||||
# 1. Create Organizations
|
||||
# ========================
|
||||
for org_data in data.get("organizations", []):
|
||||
org_result = await session.execute(
|
||||
select(Organization).where(Organization.slug == org_data["slug"])
|
||||
)
|
||||
existing_org = org_result.scalar_one_or_none()
|
||||
|
||||
if not existing_org:
|
||||
org = Organization(
|
||||
@@ -117,29 +202,20 @@ async def load_demo_data(session):
|
||||
is_active=True,
|
||||
)
|
||||
session.add(org)
|
||||
await session.flush() # Flush to get ID
|
||||
org_map[org.slug] = org
|
||||
await session.flush()
|
||||
org_map[str(org.slug)] = org
|
||||
logger.info(f"Created demo organization: {org.name}")
|
||||
else:
|
||||
# We can't easily get the ORM object from raw SQL result for map without querying again or mapping
|
||||
# So let's just query it properly if we need it for relationships
|
||||
# But for simplicity in this script, let's just assume we created it or it exists.
|
||||
# To properly map for users, we need the ID.
|
||||
# Let's use a simpler approach: just try to create, if slug conflict, skip.
|
||||
pass
|
||||
org_map[str(existing_org.slug)] = existing_org
|
||||
|
||||
# Re-query all orgs to build map for users
|
||||
result = await session.execute(select(Organization))
|
||||
orgs = result.scalars().all()
|
||||
org_map = {org.slug: org for org in orgs}
|
||||
|
||||
# Create Users
|
||||
# ========================
|
||||
# 2. Create Users
|
||||
# ========================
|
||||
for user_data in data.get("users", []):
|
||||
existing_user = await user_crud.get_by_email(
|
||||
session, email=user_data["email"]
|
||||
)
|
||||
if not existing_user:
|
||||
# Create user
|
||||
user_in = UserCreate(
|
||||
email=user_data["email"],
|
||||
password=user_data["password"],
|
||||
@@ -151,17 +227,13 @@ async def load_demo_data(session):
|
||||
user = await user_crud.create(session, obj_in=user_in)
|
||||
|
||||
# Randomize created_at for demo data (last 30 days)
|
||||
# This makes the charts look more realistic
|
||||
days_ago = random.randint(0, 30) # noqa: S311
|
||||
random_time = datetime.now(UTC) - timedelta(days=days_ago)
|
||||
# Add some random hours/minutes variation
|
||||
random_time = random_time.replace(
|
||||
hour=random.randint(0, 23), # noqa: S311
|
||||
minute=random.randint(0, 59), # noqa: S311
|
||||
)
|
||||
|
||||
# Update the timestamp and is_active directly in the database
|
||||
# We do this to ensure the values are persisted correctly
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE users SET created_at = :created_at, is_active = :is_active WHERE id = :user_id"
|
||||
@@ -174,7 +246,7 @@ async def load_demo_data(session):
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Created demo user: {user.email} (created {days_ago} days ago, active={user_data.get('is_active', True)})"
|
||||
f"Created demo user: {user.email} (created {days_ago} days ago)"
|
||||
)
|
||||
|
||||
# Add to organization if specified
|
||||
@@ -182,19 +254,219 @@ async def load_demo_data(session):
|
||||
role = user_data.get("role")
|
||||
if org_slug and org_slug in org_map and role:
|
||||
org = org_map[org_slug]
|
||||
# Check if membership exists (it shouldn't for new user)
|
||||
member = UserOrganization(
|
||||
user_id=user.id, organization_id=org.id, role=role
|
||||
)
|
||||
session.add(member)
|
||||
logger.info(f"Added {user.email} to {org.name} as {role}")
|
||||
|
||||
user_map[str(user.email)] = user
|
||||
else:
|
||||
logger.info(f"Demo user already exists: {existing_user.email}")
|
||||
user_map[str(existing_user.email)] = existing_user
|
||||
logger.debug(f"Demo user already exists: {existing_user.email}")
|
||||
|
||||
await session.flush()
|
||||
|
||||
# ========================
|
||||
# 3. Load Agent Types Map (for FK resolution)
|
||||
# ========================
|
||||
agent_types_result = await session.execute(select(AgentType))
|
||||
for at in agent_types_result.scalars().all():
|
||||
agent_type_map[str(at.slug)] = at
|
||||
|
||||
# ========================
|
||||
# 4. Create Projects
|
||||
# ========================
|
||||
for project_data in data.get("projects", []):
|
||||
project_result = await session.execute(
|
||||
select(Project).where(Project.slug == project_data["slug"])
|
||||
)
|
||||
existing_project = project_result.scalar_one_or_none()
|
||||
|
||||
if not existing_project:
|
||||
# Resolve owner email to user ID
|
||||
owner_id = None
|
||||
owner_email = project_data.get("owner_email")
|
||||
if owner_email and owner_email in user_map:
|
||||
owner_id = user_map[owner_email].id
|
||||
|
||||
project = Project(
|
||||
name=project_data["name"],
|
||||
slug=project_data["slug"],
|
||||
description=project_data.get("description"),
|
||||
owner_id=owner_id,
|
||||
autonomy_level=AutonomyLevel(
|
||||
project_data.get("autonomy_level", "milestone")
|
||||
),
|
||||
status=ProjectStatus(project_data.get("status", "active")),
|
||||
complexity=ProjectComplexity(
|
||||
project_data.get("complexity", "medium")
|
||||
),
|
||||
client_mode=ClientMode(project_data.get("client_mode", "auto")),
|
||||
settings=project_data.get("settings", {}),
|
||||
)
|
||||
session.add(project)
|
||||
await session.flush()
|
||||
project_map[str(project.slug)] = project
|
||||
logger.info(f"Created demo project: {project.name}")
|
||||
else:
|
||||
project_map[str(existing_project.slug)] = existing_project
|
||||
logger.debug(f"Demo project already exists: {existing_project.name}")
|
||||
|
||||
# ========================
|
||||
# 5. Create Sprints
|
||||
# ========================
|
||||
for sprint_data in data.get("sprints", []):
|
||||
project_slug = sprint_data["project_slug"]
|
||||
sprint_number = sprint_data["number"]
|
||||
sprint_key = f"{project_slug}:{sprint_number}"
|
||||
|
||||
if project_slug not in project_map:
|
||||
logger.warning(f"Project not found for sprint: {project_slug}")
|
||||
continue
|
||||
|
||||
sprint_project = project_map[project_slug]
|
||||
|
||||
# Check if sprint exists
|
||||
sprint_result = await session.execute(
|
||||
select(Sprint).where(
|
||||
Sprint.project_id == sprint_project.id,
|
||||
Sprint.number == sprint_number,
|
||||
)
|
||||
)
|
||||
existing_sprint = sprint_result.scalar_one_or_none()
|
||||
|
||||
if not existing_sprint:
|
||||
sprint = Sprint(
|
||||
project_id=sprint_project.id,
|
||||
name=sprint_data["name"],
|
||||
number=sprint_number,
|
||||
goal=sprint_data.get("goal"),
|
||||
start_date=date.fromisoformat(sprint_data["start_date"]),
|
||||
end_date=date.fromisoformat(sprint_data["end_date"]),
|
||||
status=SprintStatus(sprint_data.get("status", "planned")),
|
||||
planned_points=sprint_data.get("planned_points"),
|
||||
)
|
||||
session.add(sprint)
|
||||
await session.flush()
|
||||
sprint_map[sprint_key] = sprint
|
||||
logger.info(
|
||||
f"Created demo sprint: {sprint.name} for {sprint_project.name}"
|
||||
)
|
||||
else:
|
||||
sprint_map[sprint_key] = existing_sprint
|
||||
logger.debug(f"Demo sprint already exists: {existing_sprint.name}")
|
||||
|
||||
# ========================
|
||||
# 6. Create Agent Instances
|
||||
# ========================
|
||||
for agent_data in data.get("agent_instances", []):
|
||||
project_slug = agent_data["project_slug"]
|
||||
agent_type_slug = agent_data["agent_type_slug"]
|
||||
agent_name = agent_data["name"]
|
||||
agent_key = f"{project_slug}:{agent_name}"
|
||||
|
||||
if project_slug not in project_map:
|
||||
logger.warning(f"Project not found for agent: {project_slug}")
|
||||
continue
|
||||
|
||||
if agent_type_slug not in agent_type_map:
|
||||
logger.warning(f"Agent type not found: {agent_type_slug}")
|
||||
continue
|
||||
|
||||
agent_project = project_map[project_slug]
|
||||
agent_type = agent_type_map[agent_type_slug]
|
||||
|
||||
# Check if agent instance exists (by name within project)
|
||||
agent_result = await session.execute(
|
||||
select(AgentInstance).where(
|
||||
AgentInstance.project_id == agent_project.id,
|
||||
AgentInstance.name == agent_name,
|
||||
)
|
||||
)
|
||||
existing_agent = agent_result.scalar_one_or_none()
|
||||
|
||||
if not existing_agent:
|
||||
agent_instance = AgentInstance(
|
||||
project_id=agent_project.id,
|
||||
agent_type_id=agent_type.id,
|
||||
name=agent_name,
|
||||
status=AgentStatus(agent_data.get("status", "idle")),
|
||||
current_task=agent_data.get("current_task"),
|
||||
)
|
||||
session.add(agent_instance)
|
||||
await session.flush()
|
||||
agent_instance_map[agent_key] = agent_instance
|
||||
logger.info(
|
||||
f"Created demo agent: {agent_name} ({agent_type.name}) "
|
||||
f"for {agent_project.name}"
|
||||
)
|
||||
else:
|
||||
agent_instance_map[agent_key] = existing_agent
|
||||
logger.debug(f"Demo agent already exists: {existing_agent.name}")
|
||||
|
||||
# ========================
|
||||
# 7. Create Issues
|
||||
# ========================
|
||||
for issue_data in data.get("issues", []):
|
||||
project_slug = issue_data["project_slug"]
|
||||
|
||||
if project_slug not in project_map:
|
||||
logger.warning(f"Project not found for issue: {project_slug}")
|
||||
continue
|
||||
|
||||
issue_project = project_map[project_slug]
|
||||
|
||||
# Check if issue exists (by title within project - simple heuristic)
|
||||
issue_result = await session.execute(
|
||||
select(Issue).where(
|
||||
Issue.project_id == issue_project.id,
|
||||
Issue.title == issue_data["title"],
|
||||
)
|
||||
)
|
||||
existing_issue = issue_result.scalar_one_or_none()
|
||||
|
||||
if not existing_issue:
|
||||
# Resolve sprint
|
||||
sprint_id = None
|
||||
sprint_number = issue_data.get("sprint_number")
|
||||
if sprint_number:
|
||||
sprint_key = f"{project_slug}:{sprint_number}"
|
||||
if sprint_key in sprint_map:
|
||||
sprint_id = sprint_map[sprint_key].id
|
||||
|
||||
# Resolve assigned agent
|
||||
assigned_agent_id = None
|
||||
assigned_agent_name = issue_data.get("assigned_agent_name")
|
||||
if assigned_agent_name:
|
||||
agent_key = f"{project_slug}:{assigned_agent_name}"
|
||||
if agent_key in agent_instance_map:
|
||||
assigned_agent_id = agent_instance_map[agent_key].id
|
||||
|
||||
issue = Issue(
|
||||
project_id=issue_project.id,
|
||||
sprint_id=sprint_id,
|
||||
type=IssueType(issue_data.get("type", "task")),
|
||||
title=issue_data["title"],
|
||||
body=issue_data.get("body", ""),
|
||||
status=IssueStatus(issue_data.get("status", "open")),
|
||||
priority=IssuePriority(issue_data.get("priority", "medium")),
|
||||
labels=issue_data.get("labels", []),
|
||||
story_points=issue_data.get("story_points"),
|
||||
assigned_agent_id=assigned_agent_id,
|
||||
)
|
||||
session.add(issue)
|
||||
logger.info(f"Created demo issue: {issue.title[:50]}...")
|
||||
else:
|
||||
logger.debug(
|
||||
f"Demo issue already exists: {existing_issue.title[:50]}..."
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
logger.info("Demo data loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
logger.error(f"Error loading demo data: {e}")
|
||||
raise
|
||||
|
||||
@@ -210,12 +482,12 @@ async def main():
|
||||
try:
|
||||
user = await init_db()
|
||||
if user:
|
||||
print("✓ Database initialized successfully")
|
||||
print(f"✓ Superuser: {user.email}")
|
||||
print("Database initialized successfully")
|
||||
print(f"Superuser: {user.email}")
|
||||
else:
|
||||
print("✗ Failed to initialize database")
|
||||
print("Failed to initialize database")
|
||||
except Exception as e:
|
||||
print(f"✗ Error initializing database: {e}")
|
||||
print(f"Error initializing database: {e}")
|
||||
raise
|
||||
finally:
|
||||
# Close the engine
|
||||
|
||||
122
backend/data/default_agent_types.json
Normal file
122
backend/data/default_agent_types.json
Normal file
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"name": "Product Owner",
|
||||
"slug": "product-owner",
|
||||
"description": "Requirements discovery, stakeholder communication, and product vision. Leads the team in defining what to build and why.",
|
||||
"expertise": ["requirements", "stakeholder-management", "product-strategy", "user-stories", "acceptance-criteria", "prioritization"],
|
||||
"personality_prompt": "You are a skilled Product Owner focused on delivering maximum value to stakeholders. You excel at:\n- Understanding and articulating business needs\n- Writing clear user stories with acceptance criteria\n- Prioritizing features based on value and effort\n- Facilitating discussions between stakeholders and technical teams\n- Making trade-off decisions when scope conflicts arise\n\nYou communicate clearly and concisely, always keeping the end user and business goals in mind. You ask clarifying questions to ensure requirements are complete before passing them to the team.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 4096
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": ["gitea:delete_*"]
|
||||
},
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"name": "Business Analyst",
|
||||
"slug": "business-analyst",
|
||||
"description": "Analysis, documentation, and detailed specifications. Bridges the gap between business needs and technical implementation.",
|
||||
"expertise": ["analysis", "documentation", "specifications", "process-modeling", "data-analysis", "requirements-engineering"],
|
||||
"personality_prompt": "You are a meticulous Business Analyst who excels at turning vague requirements into precise specifications. You:\n- Create detailed functional and technical specifications\n- Model business processes and data flows\n- Identify edge cases and potential issues early\n- Document assumptions and dependencies clearly\n- Ensure traceability between requirements and implementation\n\nYou are thorough and detail-oriented, always considering the implications of decisions. You create documentation that developers can follow without ambiguity.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": []
|
||||
},
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"name": "Solutions Architect",
|
||||
"slug": "solutions-architect",
|
||||
"description": "System design, architecture decisions, and technical leadership. Defines the technical vision and ensures system coherence.",
|
||||
"expertise": ["system-design", "architecture", "adrs", "technical-decisions", "integration", "scalability", "security"],
|
||||
"personality_prompt": "You are an experienced Solutions Architect who designs robust, scalable systems. You:\n- Create architecture diagrams and technical documentation\n- Write Architecture Decision Records (ADRs) for key decisions\n- Evaluate technology choices based on requirements and constraints\n- Identify potential bottlenecks and security concerns\n- Ensure consistency across the system design\n\nYou think holistically about systems, considering maintainability, scalability, and operational concerns. You document your decisions with clear rationale and trade-off analysis.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.6,
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": ["gitea:create_pull_request"]
|
||||
},
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"name": "Senior Engineer",
|
||||
"slug": "senior-engineer",
|
||||
"description": "Implementation, code review, and refactoring. Writes high-quality, maintainable code following best practices.",
|
||||
"expertise": ["implementation", "code-review", "refactoring", "testing", "debugging", "performance", "clean-code"],
|
||||
"personality_prompt": "You are a Senior Software Engineer who writes clean, maintainable code. You:\n- Implement features following established patterns and standards\n- Write comprehensive tests (unit, integration, e2e)\n- Review code for correctness, performance, and maintainability\n- Refactor code to improve quality without changing behavior\n- Debug complex issues systematically\n\nYou prioritize code quality and follow SOLID principles. You write code that other developers can easily understand and maintain. You always consider edge cases and error handling.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 16384
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": ["gitea:create_pull_request", "gitea:delete_*"]
|
||||
},
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"name": "QA Engineer",
|
||||
"slug": "qa-engineer",
|
||||
"description": "Testing, quality assurance, and bug verification. Ensures the product meets quality standards before release.",
|
||||
"expertise": ["testing", "quality-assurance", "test-automation", "bug-verification", "test-planning", "regression-testing"],
|
||||
"personality_prompt": "You are a thorough QA Engineer who ensures product quality. You:\n- Design comprehensive test plans and test cases\n- Write automated tests (unit, integration, e2e)\n- Verify bug fixes and perform regression testing\n- Identify edge cases and boundary conditions\n- Document defects clearly with reproduction steps\n\nYou have a critical eye for quality and think like a user who might break things. You balance thoroughness with efficiency, focusing on high-risk areas while ensuring broad coverage.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.4,
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": []
|
||||
},
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"name": "DevOps Engineer",
|
||||
"slug": "devops-engineer",
|
||||
"description": "CI/CD, deployment, and infrastructure. Ensures reliable, automated delivery pipelines and operational excellence.",
|
||||
"expertise": ["ci-cd", "deployment", "infrastructure", "docker", "kubernetes", "monitoring", "automation"],
|
||||
"personality_prompt": "You are a skilled DevOps Engineer who builds reliable delivery pipelines. You:\n- Design and maintain CI/CD pipelines\n- Configure infrastructure as code\n- Set up monitoring, logging, and alerting\n- Automate repetitive operational tasks\n- Ensure security and compliance in deployments\n\nYou think about reliability, observability, and automation. You design systems that fail gracefully and are easy to troubleshoot. You document runbooks and operational procedures clearly.",
|
||||
"primary_model": "claude-sonnet-4-20250514",
|
||||
"fallback_models": ["claude-haiku-3-5-20241022"],
|
||||
"model_params": {
|
||||
"temperature": 0.4,
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"mcp_servers": ["gitea", "knowledge-base", "filesystem"],
|
||||
"tool_permissions": {
|
||||
"allowed": ["*"],
|
||||
"denied": [],
|
||||
"require_approval": ["gitea:create_release", "gitea:delete_*"]
|
||||
},
|
||||
"is_active": true
|
||||
}
|
||||
]
|
||||
644
backend/data/demo_data.json
Normal file
644
backend/data/demo_data.json
Normal file
@@ -0,0 +1,644 @@
|
||||
{
|
||||
"organizations": [
|
||||
{
|
||||
"name": "Acme Corp",
|
||||
"slug": "acme-corp",
|
||||
"description": "A leading provider of coyote-catching equipment."
|
||||
},
|
||||
{
|
||||
"name": "Globex Corporation",
|
||||
"slug": "globex",
|
||||
"description": "We own the East Coast."
|
||||
},
|
||||
{
|
||||
"name": "Soylent Corp",
|
||||
"slug": "soylent",
|
||||
"description": "Making food for the future."
|
||||
},
|
||||
{
|
||||
"name": "Initech",
|
||||
"slug": "initech",
|
||||
"description": "Software for the soul."
|
||||
},
|
||||
{
|
||||
"name": "Umbrella Corporation",
|
||||
"slug": "umbrella",
|
||||
"description": "Our business is life itself."
|
||||
},
|
||||
{
|
||||
"name": "Massive Dynamic",
|
||||
"slug": "massive-dynamic",
|
||||
"description": "What don't we do?"
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"email": "demo@example.com",
|
||||
"password": "DemoPass1234!",
|
||||
"first_name": "Demo",
|
||||
"last_name": "User",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "alice@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Alice",
|
||||
"last_name": "Smith",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "bob@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Bob",
|
||||
"last_name": "Jones",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "charlie@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Charlie",
|
||||
"last_name": "Brown",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "diana@acme.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Diana",
|
||||
"last_name": "Prince",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "acme-corp",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "carol@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Carol",
|
||||
"last_name": "Williams",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "owner",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "dan@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Dan",
|
||||
"last_name": "Miller",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "ellen@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Ellen",
|
||||
"last_name": "Ripley",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "fred@globex.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Fred",
|
||||
"last_name": "Flintstone",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "globex",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "dave@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Dave",
|
||||
"last_name": "Brown",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "gina@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Gina",
|
||||
"last_name": "Torres",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "harry@soylent.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Harry",
|
||||
"last_name": "Potter",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "soylent",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "eve@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Eve",
|
||||
"last_name": "Davis",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "iris@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Iris",
|
||||
"last_name": "West",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "jack@initech.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Jack",
|
||||
"last_name": "Sparrow",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "initech",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "frank@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Frank",
|
||||
"last_name": "Miller",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "george@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "George",
|
||||
"last_name": "Costanza",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "kate@umbrella.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Kate",
|
||||
"last_name": "Bishop",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "umbrella",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "leo@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Leo",
|
||||
"last_name": "Messi",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "owner",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "mary@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Mary",
|
||||
"last_name": "Jane",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "nathan@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Nathan",
|
||||
"last_name": "Drake",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "olivia@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Olivia",
|
||||
"last_name": "Dunham",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "peter@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Peter",
|
||||
"last_name": "Parker",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "quinn@massive.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Quinn",
|
||||
"last_name": "Mallory",
|
||||
"is_superuser": false,
|
||||
"organization_slug": "massive-dynamic",
|
||||
"role": "member",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "grace@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Grace",
|
||||
"last_name": "Hopper",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "heidi@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Heidi",
|
||||
"last_name": "Klum",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "ivan@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Ivan",
|
||||
"last_name": "Drago",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "rachel@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Rachel",
|
||||
"last_name": "Green",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "sam@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Sam",
|
||||
"last_name": "Wilson",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "tony@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Tony",
|
||||
"last_name": "Stark",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "una@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Una",
|
||||
"last_name": "Chin-Riley",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": false
|
||||
},
|
||||
{
|
||||
"email": "victor@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Victor",
|
||||
"last_name": "Von Doom",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"email": "wanda@example.com",
|
||||
"password": "Demo123!",
|
||||
"first_name": "Wanda",
|
||||
"last_name": "Maximoff",
|
||||
"is_superuser": false,
|
||||
"organization_slug": null,
|
||||
"role": null,
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"name": "E-Commerce Platform Redesign",
|
||||
"slug": "ecommerce-redesign",
|
||||
"description": "Complete redesign of the e-commerce platform with modern UX, improved checkout flow, and mobile-first approach.",
|
||||
"owner_email": "demo@example.com",
|
||||
"autonomy_level": "milestone",
|
||||
"status": "active",
|
||||
"complexity": "complex",
|
||||
"client_mode": "technical",
|
||||
"settings": {
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"notification_email": "demo@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mobile Banking App",
|
||||
"slug": "mobile-banking",
|
||||
"description": "Secure mobile banking application with biometric authentication, transaction history, and real-time notifications.",
|
||||
"owner_email": "alice@acme.com",
|
||||
"autonomy_level": "full_control",
|
||||
"status": "active",
|
||||
"complexity": "complex",
|
||||
"client_mode": "technical",
|
||||
"settings": {
|
||||
"mcp_servers": ["gitea", "knowledge-base"],
|
||||
"security_level": "high"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Internal HR Portal",
|
||||
"slug": "hr-portal",
|
||||
"description": "Employee self-service portal for leave requests, performance reviews, and document management.",
|
||||
"owner_email": "carol@globex.com",
|
||||
"autonomy_level": "autonomous",
|
||||
"status": "active",
|
||||
"complexity": "medium",
|
||||
"client_mode": "auto",
|
||||
"settings": {
|
||||
"mcp_servers": ["gitea", "knowledge-base"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sprints": [
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"name": "Sprint 1: Foundation",
|
||||
"number": 1,
|
||||
"goal": "Set up project infrastructure, design system, and core navigation components.",
|
||||
"start_date": "2026-01-06",
|
||||
"end_date": "2026-01-20",
|
||||
"status": "active",
|
||||
"planned_points": 21
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"name": "Sprint 2: Product Catalog",
|
||||
"number": 2,
|
||||
"goal": "Implement product listing, filtering, search, and detail pages.",
|
||||
"start_date": "2026-01-20",
|
||||
"end_date": "2026-02-03",
|
||||
"status": "planned",
|
||||
"planned_points": 34
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"name": "Sprint 1: Authentication",
|
||||
"number": 1,
|
||||
"goal": "Implement secure login, biometric authentication, and session management.",
|
||||
"start_date": "2026-01-06",
|
||||
"end_date": "2026-01-20",
|
||||
"status": "active",
|
||||
"planned_points": 26
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"name": "Sprint 1: Core Features",
|
||||
"number": 1,
|
||||
"goal": "Build employee dashboard, leave request system, and basic document management.",
|
||||
"start_date": "2026-01-06",
|
||||
"end_date": "2026-01-20",
|
||||
"status": "active",
|
||||
"planned_points": 18
|
||||
}
|
||||
],
|
||||
"agent_instances": [
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"agent_type_slug": "product-owner",
|
||||
"name": "Aria",
|
||||
"status": "idle"
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"agent_type_slug": "solutions-architect",
|
||||
"name": "Marcus",
|
||||
"status": "idle"
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"agent_type_slug": "senior-engineer",
|
||||
"name": "Zara",
|
||||
"status": "working",
|
||||
"current_task": "Implementing responsive navigation component"
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"agent_type_slug": "product-owner",
|
||||
"name": "Felix",
|
||||
"status": "waiting",
|
||||
"current_task": "Awaiting security requirements clarification"
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"agent_type_slug": "senior-engineer",
|
||||
"name": "Luna",
|
||||
"status": "working",
|
||||
"current_task": "Implementing biometric authentication flow"
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"agent_type_slug": "qa-engineer",
|
||||
"name": "Rex",
|
||||
"status": "idle"
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"agent_type_slug": "business-analyst",
|
||||
"name": "Nova",
|
||||
"status": "working",
|
||||
"current_task": "Documenting leave request workflow"
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"agent_type_slug": "senior-engineer",
|
||||
"name": "Atlas",
|
||||
"status": "working",
|
||||
"current_task": "Building employee dashboard API"
|
||||
}
|
||||
],
|
||||
"issues": [
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"sprint_number": 1,
|
||||
"type": "story",
|
||||
"title": "Design responsive navigation component",
|
||||
"body": "As a user, I want a navigation menu that works seamlessly on both desktop and mobile devices.\n\n## Acceptance Criteria\n- Hamburger menu on mobile viewports\n- Sticky header on scroll\n- Keyboard accessible\n- Screen reader compatible",
|
||||
"status": "in_progress",
|
||||
"priority": "high",
|
||||
"labels": ["frontend", "design-system"],
|
||||
"story_points": 5,
|
||||
"assigned_agent_name": "Zara"
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"sprint_number": 1,
|
||||
"type": "task",
|
||||
"title": "Set up Tailwind CSS configuration",
|
||||
"body": "Configure Tailwind CSS with custom design tokens for the e-commerce platform.\n\n- Define color palette\n- Set up typography scale\n- Configure breakpoints\n- Add custom utilities",
|
||||
"status": "closed",
|
||||
"priority": "high",
|
||||
"labels": ["frontend", "infrastructure"],
|
||||
"story_points": 3
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"sprint_number": 1,
|
||||
"type": "task",
|
||||
"title": "Create base component library structure",
|
||||
"body": "Set up the foundational component library with:\n- Button variants\n- Form inputs\n- Card component\n- Modal system",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"labels": ["frontend", "design-system"],
|
||||
"story_points": 8
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"sprint_number": 1,
|
||||
"type": "story",
|
||||
"title": "Implement user authentication flow",
|
||||
"body": "As a user, I want to sign up, log in, and manage my account.\n\n## Features\n- Email/password registration\n- Social login (Google, GitHub)\n- Password reset flow\n- Email verification",
|
||||
"status": "open",
|
||||
"priority": "critical",
|
||||
"labels": ["auth", "backend", "frontend"],
|
||||
"story_points": 13
|
||||
},
|
||||
{
|
||||
"project_slug": "ecommerce-redesign",
|
||||
"sprint_number": 2,
|
||||
"type": "epic",
|
||||
"title": "Product Catalog System",
|
||||
"body": "Complete product catalog implementation including:\n- Product listing with pagination\n- Advanced filtering and search\n- Product detail pages\n- Category navigation",
|
||||
"status": "open",
|
||||
"priority": "high",
|
||||
"labels": ["catalog", "backend", "frontend"],
|
||||
"story_points": null
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"sprint_number": 1,
|
||||
"type": "story",
|
||||
"title": "Implement biometric authentication",
|
||||
"body": "As a user, I want to log in using Face ID or Touch ID for quick and secure access.\n\n## Requirements\n- Support Face ID on iOS\n- Support fingerprint on Android\n- Fallback to PIN/password\n- Secure keychain storage",
|
||||
"status": "in_progress",
|
||||
"priority": "critical",
|
||||
"labels": ["auth", "security", "mobile"],
|
||||
"story_points": 8,
|
||||
"assigned_agent_name": "Luna"
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"sprint_number": 1,
|
||||
"type": "task",
|
||||
"title": "Set up secure session management",
|
||||
"body": "Implement secure session handling with:\n- JWT tokens with short expiry\n- Refresh token rotation\n- Session timeout handling\n- Multi-device session management",
|
||||
"status": "open",
|
||||
"priority": "critical",
|
||||
"labels": ["auth", "security", "backend"],
|
||||
"story_points": 5
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"sprint_number": 1,
|
||||
"type": "bug",
|
||||
"title": "Fix token refresh race condition",
|
||||
"body": "When multiple API calls happen simultaneously after token expiry, multiple refresh requests are made causing 401 errors.\n\n## Steps to Reproduce\n1. Wait for token to expire\n2. Trigger multiple API calls at once\n3. Observe multiple 401 errors",
|
||||
"status": "open",
|
||||
"priority": "high",
|
||||
"labels": ["bug", "auth", "backend"],
|
||||
"story_points": 3
|
||||
},
|
||||
{
|
||||
"project_slug": "mobile-banking",
|
||||
"sprint_number": 1,
|
||||
"type": "task",
|
||||
"title": "Implement PIN entry screen",
|
||||
"body": "Create secure PIN entry component with:\n- Masked input display\n- Haptic feedback\n- Brute force protection (lockout after 5 attempts)\n- Secure PIN storage",
|
||||
"status": "open",
|
||||
"priority": "high",
|
||||
"labels": ["auth", "mobile", "frontend"],
|
||||
"story_points": 5
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"sprint_number": 1,
|
||||
"type": "story",
|
||||
"title": "Build employee dashboard",
|
||||
"body": "As an employee, I want a dashboard showing my key information at a glance.\n\n## Dashboard Widgets\n- Leave balance\n- Pending approvals\n- Upcoming holidays\n- Recent announcements",
|
||||
"status": "in_progress",
|
||||
"priority": "high",
|
||||
"labels": ["frontend", "dashboard"],
|
||||
"story_points": 5,
|
||||
"assigned_agent_name": "Atlas"
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"sprint_number": 1,
|
||||
"type": "story",
|
||||
"title": "Implement leave request system",
|
||||
"body": "As an employee, I want to submit and track leave requests.\n\n## Features\n- Submit leave request with date range\n- View leave balance by type\n- Track request status\n- Manager approval workflow",
|
||||
"status": "in_progress",
|
||||
"priority": "high",
|
||||
"labels": ["backend", "frontend", "workflow"],
|
||||
"story_points": 8,
|
||||
"assigned_agent_name": "Nova"
|
||||
},
|
||||
{
|
||||
"project_slug": "hr-portal",
|
||||
"sprint_number": 1,
|
||||
"type": "task",
|
||||
"title": "Set up document storage integration",
|
||||
"body": "Integrate with S3-compatible storage for employee documents:\n- Secure upload/download\n- File type validation\n- Size limits\n- Virus scanning",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"labels": ["backend", "infrastructure", "storage"],
|
||||
"story_points": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user