Files
geo/server/internal/ops/app/seed.go
T
root c091ad7aed feat(server/ops-api): add operations console backend
Stand up an internal ops-api service with operator account, auth, and
audit log subsystems backed by a dedicated migrations_ops migration set.
Wire Makefile targets and the migrate Dockerfile stage so the new schema
ships alongside the existing tenant + monitoring databases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:32:50 +08:00

70 lines
1.9 KiB
Go

package app
import (
"context"
"errors"
"fmt"
"strings"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"github.com/geo-platform/tenant-api/internal/ops/domain"
"github.com/geo-platform/tenant-api/internal/ops/repository"
)
type DefaultAdminSeed struct {
Username string
Password string
DisplayName string
Email string
}
// SeedDefaultAdmin inserts a single admin account when the operator_accounts
// table is empty. It is safe to call on every startup — a non-empty table is
// a no-op. Returns an error only when the table is empty AND the seed config
// is incomplete (so a fresh deployment cannot silently boot without any
// way to log in).
func SeedDefaultAdmin(ctx context.Context, accounts *repository.AccountRepository, seed DefaultAdminSeed, logger *zap.Logger) error {
count, err := accounts.Count(ctx)
if err != nil {
return fmt.Errorf("count operator accounts: %w", err)
}
if count > 0 {
return nil
}
username := strings.TrimSpace(seed.Username)
password := seed.Password
if username == "" || password == "" {
return errors.New("operator_accounts is empty but default_admin.username/password are not configured (set OPS_DEFAULT_ADMIN_USERNAME / OPS_DEFAULT_ADMIN_PASSWORD)")
}
displayName := strings.TrimSpace(seed.DisplayName)
if displayName == "" {
displayName = username
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("hash default admin password: %w", err)
}
var emailPtr *string
if email := strings.TrimSpace(seed.Email); email != "" {
emailPtr = &email
}
_, err = accounts.Create(ctx, repository.CreateAccountInput{
Username: username,
Email: emailPtr,
PasswordHash: string(hash),
DisplayName: displayName,
Role: domain.RoleAdmin,
})
if err != nil {
return fmt.Errorf("create default admin: %w", err)
}
logger.Sugar().Infof("ops default admin seeded: username=%s", username)
return nil
}