dealspace/cmd/server/seed.go

245 lines
7.7 KiB
Go

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,
)
}
}