de30497f59
- 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.
161 lines
4.5 KiB
Go
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")
|
|
}
|