package main import ( "fmt" "log" "time" "github.com/google/uuid" "github.com/mish/dealspace/lib" ) // seedSuperAdmins ensures the super admin accounts always exist. // Called on every startup (not just when SEED_DEMO=true). func seedSuperAdmins(db *lib.DB) { type admin struct { email string name string orgID string orgName string } admins := []admin{ {"michael@muskepo.com", "Michael", "muskepo", "Muskepo"}, {"johan@jongsma.me", "Johan", "muskepo", "Muskepo"}, } for _, a := range admins { existing, err := lib.UserByEmail(db, a.email) if err != nil { log.Printf("seed: check %s: %v", a.email, err) continue } if existing != nil { // User exists, ensure they have super_admin access ensureSuperAdmin(db, existing.UserID) continue } now := time.Now().UnixMilli() userID := uuid.New().String() user := &lib.User{ UserID: userID, Email: a.email, Name: a.name, Password: "", // passwordless auth — no password needed OrgID: a.orgID, OrgName: a.orgName, Active: true, CreatedAt: now, UpdatedAt: now, } if err := lib.UserCreate(db, user); err != nil { log.Printf("seed: create %s: %v", a.email, err) continue } log.Printf("seed: created super admin %s", a.email) ensureSuperAdmin(db, userID) } } // ensureSuperAdmin grants super_admin role on the special "*" project (platform-wide). func ensureSuperAdmin(db *lib.DB, userID string) { // Check if already has super_admin isSuperAdmin, _ := lib.IsSuperAdmin(db, userID) if isSuperAdmin { return } now := time.Now().UnixMilli() access := &lib.Access{ ID: uuid.New().String(), ProjectID: "*", // platform-wide UserID: userID, Role: lib.RoleSuperAdmin, Ops: "rwdm", CanGrant: true, GrantedBy: "system", GrantedAt: now, } if err := lib.AccessGrant(db, access); err != nil { log.Printf("seed: grant super_admin to %s: %v", userID, err) return } log.Printf("seed: granted super_admin to %s", userID) } func seedDemoData(db *lib.DB, cfg *lib.Config) { count, err := lib.UserCount(db) if err != nil { log.Printf("seed: cannot check user count: %v", err) return } // Only seed demo data if no users exist (besides super admins) if count > 2 { log.Printf("seed: users already exist, skipping demo data") return } log.Printf("seed: creating demo data...") now := time.Now().UnixMilli() // Create demo user: admin@demo.com (passwordless — no password needed) adminID := uuid.New().String() admin := &lib.User{ UserID: adminID, Email: "admin@demo.com", Name: "Demo Admin", Password: "", OrgID: "demo-org", OrgName: "Demo Investment Bank", Active: true, CreatedAt: now, UpdatedAt: now, } if err := lib.UserCreate(db, admin); err != nil { log.Printf("seed: create admin: %v", err) return } log.Printf("seed: created admin user admin@demo.com") // Create demo project: TechCorp Acquisition projectID := uuid.New().String() projectData := `{"name":"TechCorp Acquisition","deal_type":"sell_side","status":"active"}` key, err := lib.DeriveProjectKey(cfg.MasterKey, projectID) if err != nil { log.Printf("seed: derive key: %v", err) return } summaryPacked, _ := lib.Pack(key, "TechCorp Acquisition") dataPacked, _ := lib.Pack(key, projectData) _, err = db.Conn.Exec( `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, search_key2, summary, data, stage, assignee_id, return_to_id, origin_id, version, key_version, created_at, updated_at, created_by) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?,?)`, projectID, projectID, "", lib.TypeProject, 0, nil, nil, summaryPacked, dataPacked, lib.StagePreDataroom, "", "", "", 1, 1, now, now, adminID, ) if err != nil { log.Printf("seed: create project: %v", err) return } // Grant ib_admin access accessID := uuid.New().String() _, err = db.Conn.Exec( `INSERT INTO access (id, project_id, workstream_id, user_id, role, ops, can_grant, granted_by, granted_at) VALUES (?,?,?,?,?,?,?,?,?)`, accessID, projectID, nil, adminID, lib.RoleIBAdmin, "rwdm", 1, adminID, now, ) if err != nil { log.Printf("seed: grant access: %v", err) return } // Also grant super admins access to the demo project grantSuperAdminsToProject(db, projectID, now) // Create workstreams: Finance, Legal, IT workstreams := []string{"Finance", "Legal", "IT"} wsIDs := make([]string, len(workstreams)) for i, name := range workstreams { wsID := uuid.New().String() wsIDs[i] = wsID wsSummary, _ := lib.Pack(key, name) wsData, _ := lib.Pack(key, fmt.Sprintf(`{"name":"%s"}`, name)) _, err = db.Conn.Exec( `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, search_key2, summary, data, stage, assignee_id, return_to_id, origin_id, version, key_version, created_at, updated_at, created_by) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?,?)`, wsID, projectID, projectID, lib.TypeWorkstream, 1, nil, nil, wsSummary, wsData, lib.StagePreDataroom, "", "", "", 1, 1, now, now, adminID, ) if err != nil { log.Printf("seed: create workstream %s: %v", name, err) } } log.Printf("seed: created 3 workstreams") // Create 5 sample requests in Finance workstream type seedReq struct { ref, title, body, priority, due string } requests := []seedReq{ {"FIN-001", "Audited financial statements FY2023-2025", "Provide complete audited financial statements for fiscal years 2023, 2024, and 2025.", "high", "2026-03-15"}, {"FIN-002", "Revenue breakdown by customer (top 20)", "Break down revenue by top 20 customers showing percentage of total revenue.", "high", "2026-03-15"}, {"FIN-003", "Cap table and option pool details", "Provide current capitalization table including all share classes and warrants.", "normal", "2026-03-20"}, {"FIN-004", "AR/AP aging reports", "Accounts receivable and accounts payable aging reports as of most recent month-end.", "normal", "2026-03-25"}, {"FIN-005", "Revenue recognition policy", "Document your revenue recognition policy including any changes in past 3 fiscal years.", "low", "2026-03-30"}, } for _, req := range requests { reqID := uuid.New().String() reqData := fmt.Sprintf(`{"title":"%s","body":"%s","priority":"%s","due_date":"%s","status":"open","ref":"%s"}`, req.title, req.body, req.priority, req.due, req.ref) reqSummary, _ := lib.Pack(key, req.title) reqDataPacked, _ := lib.Pack(key, reqData) _, err = db.Conn.Exec( `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, search_key, search_key2, summary, data, stage, assignee_id, return_to_id, origin_id, version, key_version, created_at, updated_at, created_by) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?,?)`, reqID, projectID, wsIDs[0], lib.TypeRequest, 3, nil, nil, reqSummary, reqDataPacked, lib.StagePreDataroom, adminID, "", "", 1, 1, now, now, adminID, ) if err != nil { log.Printf("seed: create request %s: %v", req.ref, err) } } log.Printf("seed: created 5 sample requests in Finance workstream") log.Printf("seed: done! Login with admin@demo.com, michael@muskepo.com, or johan@jongsma.me") } // grantSuperAdminsToProject gives super admin users ib_admin access on a project. func grantSuperAdminsToProject(db *lib.DB, projectID string, now int64) { emails := []string{"michael@muskepo.com", "johan@jongsma.me"} for _, email := range emails { user, err := lib.UserByEmail(db, email) if err != nil || user == nil { continue } accessID := uuid.New().String() _, _ = db.Conn.Exec( `INSERT OR IGNORE INTO access (id, project_id, workstream_id, user_id, role, ops, can_grant, granted_by, granted_at) VALUES (?,?,?,?,?,?,?,?,?)`, accessID, projectID, nil, user.UserID, lib.RoleIBAdmin, "rwdm", 1, "system", now, ) } }