feat(ops-api/site-mappings): provision monitoring DB pool and readiness check

Open a separate pgx pool for the monitoring database, wire it into the
SiteDomainMappingService, and surface its health in /readyz. Point compose
at the dedicated monitoring-postgres service and align local/dev configs
with the per-instance ports. Also reformat the site_domain_mapping types
with gofmt while the files are touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 17:10:48 +08:00
parent 665405763c
commit bef55bd11f
9 changed files with 84 additions and 65 deletions
+2
View File
@@ -240,6 +240,8 @@ services:
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
monitoring-postgres:
condition: service_healthy
migrate: migrate:
condition: service_completed_successfully condition: service_completed_successfully
ports: ports:
+1 -1
View File
@@ -13,7 +13,7 @@ database:
max_idle_conns: 2 max_idle_conns: 2
monitoring_database: monitoring_database:
host: postgres host: monitoring-postgres
port: 5432 port: 5432
user: geo user: geo
password: geo_dev password: geo_dev
+18 -8
View File
@@ -44,9 +44,16 @@ func main() {
} }
defer pool.Close() defer pool.Close()
monitoringPool, err := postgres.NewPool(ctx, cfg.MonitoringDatabase)
if err != nil {
logger.Sugar().Fatalf("ops-api init monitoring postgres: %v", err)
}
defer monitoringPool.Close()
accountsRepo := repository.NewAccountRepository(pool) accountsRepo := repository.NewAccountRepository(pool)
adminUsersRepo := repository.NewAdminUserRepository(pool) adminUsersRepo := repository.NewAdminUserRepository(pool)
auditsRepo := repository.NewAuditRepository(pool) auditsRepo := repository.NewAuditRepository(pool)
siteDomainMappingsRepo := repository.NewSiteDomainMappingRepository(monitoringPool)
ipRegionResolver, err := app.NewIPRegionResolver(cfg.IPRegion.V4XDBPath, cfg.IPRegion.V6XDBPath, logger) ipRegionResolver, err := app.NewIPRegionResolver(cfg.IPRegion.V4XDBPath, cfg.IPRegion.V6XDBPath, logger)
if err != nil { if err != nil {
@@ -59,6 +66,7 @@ func main() {
authSvc := app.NewAuthService(accountsRepo, auditSvc, issuer) authSvc := app.NewAuthService(accountsRepo, auditSvc, issuer)
accountSvc := app.NewAccountService(accountsRepo, auditSvc) accountSvc := app.NewAccountService(accountsRepo, auditSvc)
adminUserSvc := app.NewAdminUserService(adminUsersRepo, auditSvc, cfg.AdminUsers.DefaultPlanCode) adminUserSvc := app.NewAdminUserService(adminUsersRepo, auditSvc, cfg.AdminUsers.DefaultPlanCode)
siteDomainMappingSvc := app.NewSiteDomainMappingService(siteDomainMappingsRepo, auditSvc)
if err := app.SeedDefaultAdmin(ctx, accountsRepo, app.DefaultAdminSeed{ if err := app.SeedDefaultAdmin(ctx, accountsRepo, app.DefaultAdminSeed{
Username: cfg.DefaultAdmin.Username, Username: cfg.DefaultAdmin.Username,
@@ -75,14 +83,16 @@ func main() {
engine := gin.New() engine := gin.New()
transport.RegisterRoutes(transport.Deps{ transport.RegisterRoutes(transport.Deps{
Engine: engine, Engine: engine,
Logger: logger, Logger: logger,
DB: pool, DB: pool,
Issuer: issuer, MonitoringDB: monitoringPool,
Auth: authSvc, Issuer: issuer,
Accounts: accountSvc, Auth: authSvc,
AdminUsers: adminUserSvc, Accounts: accountSvc,
Audits: auditSvc, AdminUsers: adminUserSvc,
Audits: auditSvc,
SiteDomains: siteDomainMappingSvc,
}) })
addr := fmt.Sprintf(":%d", cfg.Server.Port) addr := fmt.Sprintf(":%d", cfg.Server.Port)
+1 -1
View File
@@ -14,7 +14,7 @@ database:
monitoring_database: monitoring_database:
host: localhost host: localhost
port: 5432 port: 5433
user: geo user: geo
password: geo_dev password: geo_dev
dbname: geo_monitoring dbname: geo_monitoring
+28 -28
View File
@@ -16,11 +16,11 @@ import (
) )
const ( const (
ActionSiteDomainMappingCreate = "site_domain_mapping.create" ActionSiteDomainMappingCreate = "site_domain_mapping.create"
ActionSiteDomainMappingUpdate = "site_domain_mapping.update" ActionSiteDomainMappingUpdate = "site_domain_mapping.update"
ActionSiteDomainMappingEnable = "site_domain_mapping.enable" ActionSiteDomainMappingEnable = "site_domain_mapping.enable"
ActionSiteDomainMappingDisable = "site_domain_mapping.disable" ActionSiteDomainMappingDisable = "site_domain_mapping.disable"
ActionSiteDomainMappingDelete = "site_domain_mapping.delete" ActionSiteDomainMappingDelete = "site_domain_mapping.delete"
) )
type SiteDomainMappingService struct { type SiteDomainMappingService struct {
@@ -33,13 +33,13 @@ func NewSiteDomainMappingService(mappings *repository.SiteDomainMappingRepositor
} }
type SiteDomainMappingView struct { type SiteDomainMappingView struct {
ID int64 `json:"id"` ID int64 `json:"id"`
RegistrableDomain string `json:"registrable_domain"` RegistrableDomain string `json:"registrable_domain"`
SiteKey string `json:"site_key"` SiteKey string `json:"site_key"`
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
type SiteDomainMappingListInput struct { type SiteDomainMappingListInput struct {
@@ -58,20 +58,20 @@ type SiteDomainMappingListResult struct {
type SiteDomainMappingInput struct { type SiteDomainMappingInput struct {
RegistrableDomain string RegistrableDomain string
SiteKey string SiteKey string
SiteName string SiteName string
IsActive bool IsActive bool
} }
func toSiteDomainMappingView(item *domain.SiteDomainMapping) SiteDomainMappingView { func toSiteDomainMappingView(item *domain.SiteDomainMapping) SiteDomainMappingView {
return SiteDomainMappingView{ return SiteDomainMappingView{
ID: item.ID, ID: item.ID,
RegistrableDomain: item.RegistrableDomain, RegistrableDomain: item.RegistrableDomain,
SiteKey: item.SiteKey, SiteKey: item.SiteKey,
SiteName: item.SiteName, SiteName: item.SiteName,
IsActive: item.IsActive, IsActive: item.IsActive,
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
} }
} }
@@ -109,9 +109,9 @@ func (s *SiteDomainMappingService) Create(ctx context.Context, actor *Actor, in
} }
item, err := s.mappings.Create(ctx, repository.CreateSiteDomainMappingInput{ item, err := s.mappings.Create(ctx, repository.CreateSiteDomainMappingInput{
RegistrableDomain: normalized.RegistrableDomain, RegistrableDomain: normalized.RegistrableDomain,
SiteKey: normalized.SiteKey, SiteKey: normalized.SiteKey,
SiteName: normalized.SiteName, SiteName: normalized.SiteName,
IsActive: normalized.IsActive, IsActive: normalized.IsActive,
}) })
if err != nil { if err != nil {
if isDuplicateSiteDomainMapping(err) { if isDuplicateSiteDomainMapping(err) {
@@ -133,9 +133,9 @@ func (s *SiteDomainMappingService) Update(ctx context.Context, actor *Actor, id
} }
item, err := s.mappings.Update(ctx, id, repository.UpdateSiteDomainMappingInput{ item, err := s.mappings.Update(ctx, id, repository.UpdateSiteDomainMappingInput{
RegistrableDomain: normalized.RegistrableDomain, RegistrableDomain: normalized.RegistrableDomain,
SiteKey: normalized.SiteKey, SiteKey: normalized.SiteKey,
SiteName: normalized.SiteName, SiteName: normalized.SiteName,
IsActive: normalized.IsActive, IsActive: normalized.IsActive,
}) })
if err != nil { if err != nil {
if errors.Is(err, repository.ErrSiteDomainMappingNotFound) { if errors.Is(err, repository.ErrSiteDomainMappingNotFound) {
@@ -213,9 +213,9 @@ func normalizeSiteDomainMappingInput(in SiteDomainMappingInput) (SiteDomainMappi
return SiteDomainMappingInput{ return SiteDomainMappingInput{
RegistrableDomain: domainName, RegistrableDomain: domainName,
SiteKey: siteKey, SiteKey: siteKey,
SiteName: siteName, SiteName: siteName,
IsActive: in.IsActive, IsActive: in.IsActive,
}, nil }, nil
} }
+6 -6
View File
@@ -65,11 +65,11 @@ type AuditLogFilter struct {
} }
type SiteDomainMapping struct { type SiteDomainMapping struct {
ID int64 ID int64
RegistrableDomain string RegistrableDomain string
SiteKey string SiteKey string
SiteName string SiteName string
IsActive bool IsActive bool
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
} }
@@ -32,16 +32,16 @@ type SiteDomainMappingFilter struct {
type CreateSiteDomainMappingInput struct { type CreateSiteDomainMappingInput struct {
RegistrableDomain string RegistrableDomain string
SiteKey string SiteKey string
SiteName string SiteName string
IsActive bool IsActive bool
} }
type UpdateSiteDomainMappingInput struct { type UpdateSiteDomainMappingInput struct {
RegistrableDomain string RegistrableDomain string
SiteKey string SiteKey string
SiteName string SiteName string
IsActive bool IsActive bool
} }
func scanSiteDomainMapping(row pgx.Row) (*domain.SiteDomainMapping, error) { func scanSiteDomainMapping(row pgx.Row) (*domain.SiteDomainMapping, error) {
+16 -9
View File
@@ -11,15 +11,16 @@ import (
) )
type Deps struct { type Deps struct {
Engine *gin.Engine Engine *gin.Engine
Logger *zap.Logger Logger *zap.Logger
DB *pgxpool.Pool DB *pgxpool.Pool
Issuer *app.TokenIssuer MonitoringDB *pgxpool.Pool
Auth *app.AuthService Issuer *app.TokenIssuer
Accounts *app.AccountService Auth *app.AuthService
AdminUsers *app.AdminUserService Accounts *app.AccountService
Audits *app.AuditService AdminUsers *app.AdminUserService
SiteDomains *app.SiteDomainMappingService Audits *app.AuditService
SiteDomains *app.SiteDomainMappingService
} }
func RegisterRoutes(d Deps) { func RegisterRoutes(d Deps) {
@@ -38,6 +39,12 @@ func RegisterRoutes(d Deps) {
response.Error(c, response.ErrServiceUnavailable(50301, "postgres_unavailable", "数据库不可达")) response.Error(c, response.ErrServiceUnavailable(50301, "postgres_unavailable", "数据库不可达"))
return return
} }
if d.MonitoringDB != nil {
if err := d.MonitoringDB.Ping(c.Request.Context()); err != nil {
response.Error(c, response.ErrServiceUnavailable(50303, "monitoring_postgres_unavailable", "监控数据库不可达"))
return
}
}
response.Success(c, gin.H{"status": "ready"}) response.Success(c, gin.H{"status": "ready"})
}) })
@@ -11,9 +11,9 @@ import (
type siteDomainMappingRequest struct { type siteDomainMappingRequest struct {
RegistrableDomain string `json:"registrable_domain" binding:"required"` RegistrableDomain string `json:"registrable_domain" binding:"required"`
SiteKey string `json:"site_key"` SiteKey string `json:"site_key"`
SiteName string `json:"site_name" binding:"required"` SiteName string `json:"site_name" binding:"required"`
IsActive *bool `json:"is_active"` IsActive *bool `json:"is_active"`
} }
type setSiteDomainMappingActiveRequest struct { type setSiteDomainMappingActiveRequest struct {
@@ -118,9 +118,9 @@ func (r siteDomainMappingRequest) toInput() app.SiteDomainMappingInput {
} }
return app.SiteDomainMappingInput{ return app.SiteDomainMappingInput{
RegistrableDomain: r.RegistrableDomain, RegistrableDomain: r.RegistrableDomain,
SiteKey: r.SiteKey, SiteKey: r.SiteKey,
SiteName: r.SiteName, SiteName: r.SiteName,
IsActive: active, IsActive: active,
} }
} }