package svc import ( "strings" "apps/backend/internal/config" "apps/backend/internal/middleware" fsDomain "apps/backend/internal/module/filestorage/domain" "apps/backend/internal/module/filestorage/noop" "apps/backend/internal/module/filestorage/s3store" memberDomain "apps/backend/internal/module/member/domain" memberRepo "apps/backend/internal/module/member/repository" memberUC "apps/backend/internal/module/member/usecase" notifDomain "apps/backend/internal/module/notification/domain" notifUC "apps/backend/internal/module/notification/usecase" tokenUC "apps/backend/internal/module/token/usecase" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest" ) // ServiceContext wires goctl middleware + modules. // // 持久化(關機不丟): // - members / settings / refresh_tokens / auth_codes → Mongo // Redis(可丟、可重建): // - monc 實體快取 only(CacheRedis) type ServiceContext struct { Config config.Config AuthJWT rest.Middleware AdminAuth rest.Middleware Members memberDomain.Repository Auth *memberUC.AuthService Token *tokenUC.JWTIssuer Notification notifDomain.UseCase Storage fsDomain.Storage } func NewServiceContext(c config.Config) *ServiceContext { if len(c.CacheRedis) == 0 { logx.Must(errString("CacheRedis is required for monc entity cache")) } repo := memberRepo.NewMoncStore(c.Mongo.URI, c.Mongo.Database, c.CacheRedis) logx.Infof("mongo monc ready db=%s redis_cache=%s", c.Mongo.Database, c.CacheRedis[0].Host) issuer := tokenUC.NewJWTIssuer( c.Auth.AccessSecret, c.Auth.RefreshSecret, c.Auth.AccessExpire, c.Auth.RefreshExpire, ) cost := c.Bcrypt.Cost if cost <= 0 { cost = 10 } auth := memberUC.NewAccountService(repo, issuer, cost) auth.GoogleClientID = c.OAuth.GoogleClientID auth.LineClientID = c.OAuth.LineClientID auth.LineClientSecret = c.OAuth.LineClientSecret auth.LineRedirectURI = c.OAuth.LineRedirectURI notify := newNotification(c) store := newStorage(c) return &ServiceContext{ Config: c, Members: repo, Auth: auth, Token: issuer, Notification: notify, Storage: store, AuthJWT: middleware.NewAuthJWTMiddleware(issuer, repo).Handle, AdminAuth: middleware.NewAdminAuthMiddleware().Handle, } } // newStorage 只接 MinIO/S3(S3 API);不落本機磁碟。 func newStorage(c config.Config) fsDomain.Storage { os := c.ObjectStorage if strings.TrimSpace(os.Endpoint) == "" { logx.Error("object storage: Endpoint empty — uploads disabled (MinIO/S3 required)") return noop.New() } s, err := s3store.New(s3store.Config{ Endpoint: os.Endpoint, Region: os.Region, Bucket: os.Bucket, AccessKey: os.AccessKey, SecretKey: os.SecretKey, UsePathStyle: os.UsePathStyle, PublicBaseURL: os.PublicBaseURL, }) if err != nil { logx.Errorf("object storage MinIO/S3 init failed: %v — uploads disabled", err) return noop.New() } logx.Infof("object storage: minio/s3 endpoint=%s bucket=%s", os.Endpoint, os.Bucket) return s } func newNotification(c config.Config) notifDomain.UseCase { base := strings.TrimRight(c.PublicWebBase, "/") if base == "" { base = "http://127.0.0.1:5173" } brand := notifDomain.Brand{ Name: c.Brand.Name, Link: base, LogoURL: c.Brand.LogoURL, Copyright: c.Brand.Copyright, } if brand.Name == "" { brand.Name = "Harbor Desk" } if brand.LogoURL == "" { // apps/web/public/brand-mark.jpg — 郵件客戶端需絕對 URL brand.LogoURL = base + "/brand-mark.jpg" } if brand.Copyright == "" { brand.Copyright = "© Harbor Desk" } cfg := notifUC.Config{ Sender: c.Mail.Sender, DevExposeCode: c.Mail.DevExposeCode, Brand: brand, } if c.Mail.SMTP.Host != "" { cfg.Providers = []notifDomain.EmailDelivery{ notifUC.NewSMTPDelivery(notifUC.SMTPConfig{ Host: c.Mail.SMTP.Host, Port: c.Mail.SMTP.Port, User: c.Mail.SMTP.User, Password: c.Mail.SMTP.Password, }), } logx.Infof("notification mail: smtp host=%s port=%d logo=%s expose_code=%v", c.Mail.SMTP.Host, c.Mail.SMTP.Port, brand.LogoURL, c.Mail.DevExposeCode) } else { cfg.Providers = []notifDomain.EmailDelivery{notifUC.NewLogDelivery()} logx.Info("notification mail: log-only (set Mail.SMTP.Host to send real email)") } return notifUC.NewService(cfg) } type errString string func (e errString) Error() string { return string(e) }