Files
geo/server/cmd/dev-seed/main.go
T
root de30497f59 feat: add tenant and user management with migrations, handlers, and tests
- Implemented tenant and user management features including:
  - Tenant creation and management with associated migrations.
  - User creation and management with associated migrations.
  - Tenant membership management with associated migrations.
  - Platform user roles management with associated migrations.
  - Quota management with associated migrations.
  - Article and template management with associated migrations.
- Added HTTP handlers for templates and workspaces.
- Created tests for protected and public routes.
- Introduced a script to check tenant scope in SQL queries.
- Documented task plan for backend completion and frontend foundation.
2026-04-01 00:58:42 +08:00

161 lines
4.5 KiB
Go

package main
import (
"context"
"fmt"
"log"
"os"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/crypto/bcrypt"
"github.com/geo-platform/tenant-api/internal/shared/config"
)
func main() {
configPath := "configs/config.yaml"
if p := os.Getenv("CONFIG_PATH"); p != "" {
configPath = p
}
cfg, err := config.Load(configPath)
if err != nil {
log.Fatalf("load config: %v", err)
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, cfg.Database.DSN())
if err != nil {
log.Fatalf("connect db: %v", err)
}
defer pool.Close()
hash, err := bcrypt.GenerateFromPassword([]byte("Admin@123"), bcrypt.DefaultCost)
if err != nil {
log.Fatalf("hash password: %v", err)
}
tx, err := pool.Begin(ctx)
if err != nil {
log.Fatalf("begin tx: %v", err)
}
defer func() {
_ = tx.Rollback(ctx)
}()
// Tenant
var tenantID int64
err = tx.QueryRow(ctx, `
INSERT INTO tenants (name, status) VALUES ('GEO Demo', 'active')
ON CONFLICT DO NOTHING
RETURNING id
`).Scan(&tenantID)
if err != nil {
log.Fatalf("insert tenant: %v", err)
}
// User
var userID int64
err = tx.QueryRow(ctx, `
INSERT INTO users (email, password_hash, name, status)
VALUES ('admin@geo.local', $1, 'Admin', 'active')
ON CONFLICT DO NOTHING
RETURNING id
`, string(hash)).Scan(&userID)
if err != nil {
log.Fatalf("insert user: %v", err)
}
// Membership
_, err = tx.Exec(ctx, `
INSERT INTO tenant_memberships (tenant_id, user_id, tenant_role)
VALUES ($1, $2, 'tenant_admin')
ON CONFLICT DO NOTHING
`, tenantID, userID)
if err != nil {
log.Fatalf("insert membership: %v", err)
}
// Plan
var planID int64
err = tx.QueryRow(ctx, `
INSERT INTO plans (plan_code, name, quota_policy_json, status)
VALUES ('free', 'Free Plan', '{"article_generation": 100}', 'active')
ON CONFLICT DO NOTHING
RETURNING id
`).Scan(&planID)
if err != nil {
log.Fatalf("insert plan: %v", err)
}
// Subscription
_, err = tx.Exec(ctx, `
INSERT INTO tenant_plan_subscriptions (tenant_id, plan_id, start_at, end_at, status)
VALUES ($1, $2, NOW(), NOW() + INTERVAL '1 year', 'active')
ON CONFLICT DO NOTHING
`, tenantID, planID)
if err != nil {
log.Fatalf("insert subscription: %v", err)
}
// Quota ledger
_, err = tx.Exec(ctx, `
INSERT INTO tenant_quota_ledgers (tenant_id, quota_type, delta, balance_after, reason)
VALUES ($1, 'article_generation', 100, 100, 'initial_allocation')
`, tenantID)
if err != nil {
log.Fatalf("insert quota: %v", err)
}
// Platform templates
templates := []struct {
key, name, schema, prompt string
}{
{
"top_x_article",
"Top X 文章",
`{"fields":[{"name":"topic","type":"text","label":"话题","required":true},{"name":"count","type":"number","label":"数量","required":true,"default":10}]}`,
"Write a Top {{count}} article about {{topic}}. Include brief descriptions for each item.",
},
{
"product_review",
"产品评测文章",
`{"fields":[{"name":"product_name","type":"text","label":"产品名称","required":true},{"name":"category","type":"text","label":"品类","required":true}]}`,
"Write a comprehensive product review for {{product_name}} in the {{category}} category.",
},
{
"research_report",
"研究报告文章",
`{"fields":[{"name":"subject","type":"text","label":"研究主题","required":true},{"name":"depth","type":"select","label":"深度","options":["overview","detailed"],"default":"overview"}]}`,
"Write a {{depth}} research report about {{subject}}.",
},
{
"brand_search_expansion",
"品牌词搜索扩写",
`{"fields":[{"name":"brand","type":"text","label":"品牌名","required":true},{"name":"keyword","type":"text","label":"关键词","required":true}]}`,
"Expand on the search query '{{brand}} {{keyword}}' with a comprehensive article.",
},
}
for _, t := range templates {
_, err = tx.Exec(ctx, `
INSERT INTO article_templates (scope, origin_type, template_key, template_name, schema_json, prompt_template, prompt_visibility, status)
VALUES ('platform', 'platform', $1, $2, $3, $4, 'visible', 'active')
ON CONFLICT DO NOTHING
`, t.key, t.name, t.schema, t.prompt)
if err != nil {
log.Fatalf("insert template %s: %v", t.key, err)
}
}
if err := tx.Commit(ctx); err != nil {
log.Fatalf("commit: %v", err)
}
fmt.Println("Seed data inserted successfully.")
fmt.Printf(" Tenant ID: %d\n", tenantID)
fmt.Printf(" User ID: %d (admin@geo.local / Admin@123)\n", userID)
fmt.Printf(" Plan ID: %d (free)\n", planID)
fmt.Println(" Templates: 4 platform presets")
}