diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 1c31b65..db95728 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -30,6 +30,7 @@ export interface UserInfo { tenant_id: number; tenant_role: string; permissions: string[]; + kol_profile: KolProfileBrief | null; } export interface LoginResponse { diff --git a/server/internal/bootstrap/bootstrap.go b/server/internal/bootstrap/bootstrap.go index 05f31d0..2586bc6 100644 --- a/server/internal/bootstrap/bootstrap.go +++ b/server/internal/bootstrap/bootstrap.go @@ -26,6 +26,7 @@ import ( "github.com/geo-platform/tenant-api/internal/shared/retrieval" "github.com/geo-platform/tenant-api/internal/shared/stream" "github.com/geo-platform/tenant-api/internal/tenant/prompts" + "github.com/geo-platform/tenant-api/internal/tenant/repository" ) type App struct { @@ -45,6 +46,7 @@ type App struct { AuditLogs *auditlog.AsyncWriter GenerationStreams *stream.GenerationHub Cache cache.Cache + KolProfiles repository.KolProfileRepository } func New(configPath string) (*App, error) { @@ -96,6 +98,7 @@ func New(configPath string) (*App, error) { auditLogs := auditlog.NewAsyncWriter(pool, logger) generationStreams := stream.NewGenerationHub(mqClient) appCache := cache.New(cfg.Cache.Driver, rdb) + kolProfiles := repository.NewKolProfileRepository(pool) if cfg.Server.Mode == "release" { gin.SetMode(gin.ReleaseMode) @@ -149,6 +152,7 @@ func New(configPath string) (*App, error) { AuditLogs: auditLogs, GenerationStreams: generationStreams, Cache: appCache, + KolProfiles: kolProfiles, }, nil } diff --git a/server/internal/tenant/app/auth_service.go b/server/internal/tenant/app/auth_service.go index 75d6bb1..1e5bd58 100644 --- a/server/internal/tenant/app/auth_service.go +++ b/server/internal/tenant/app/auth_service.go @@ -13,13 +13,24 @@ import ( ) type AuthService struct { - repo repository.AuthRepository - jwt *auth.Manager - sessions *auth.SessionStore + repo repository.AuthRepository + kolProfiles repository.KolProfileRepository + jwt *auth.Manager + sessions *auth.SessionStore } -func NewAuthService(repo repository.AuthRepository, jwt *auth.Manager, sessions *auth.SessionStore) *AuthService { - return &AuthService{repo: repo, jwt: jwt, sessions: sessions} +func NewAuthService( + repo repository.AuthRepository, + kolProfiles repository.KolProfileRepository, + jwt *auth.Manager, + sessions *auth.SessionStore, +) *AuthService { + return &AuthService{ + repo: repo, + kolProfiles: kolProfiles, + jwt: jwt, + sessions: sessions, + } } type LoginRequest struct { @@ -35,13 +46,21 @@ type LoginResponse struct { } type UserInfo struct { - ID int64 `json:"id"` - Email string `json:"email"` - Name string `json:"name"` - AvatarURL *string `json:"avatar_url"` - TenantID int64 `json:"tenant_id"` - TenantRole string `json:"tenant_role"` - Permissions []string `json:"permissions"` + ID int64 `json:"id"` + Email string `json:"email"` + Name string `json:"name"` + AvatarURL *string `json:"avatar_url"` + TenantID int64 `json:"tenant_id"` + TenantRole string `json:"tenant_role"` + Permissions []string `json:"permissions"` + KolProfile *KolProfileBrief `json:"kol_profile"` +} + +type KolProfileBrief struct { + ID int64 `json:"id"` + DisplayName string `json:"display_name"` + Status string `json:"status"` + MarketEnabled bool `json:"market_enabled"` } func (s *AuthService) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) { @@ -69,6 +88,11 @@ func (s *AuthService) Login(ctx context.Context, req LoginRequest) (*LoginRespon return nil, fmt.Errorf("save refresh: %w", err) } + kolProfile, err := s.loadKolProfileBrief(ctx, membership.TenantID, user.ID) + if err != nil { + return nil, err + } + return &LoginResponse{ AccessToken: pair.AccessToken, RefreshToken: pair.RefreshToken, @@ -81,6 +105,7 @@ func (s *AuthService) Login(ctx context.Context, req LoginRequest) (*LoginRespon TenantID: membership.TenantID, TenantRole: membership.TenantRole, Permissions: auth.PermissionsForRole(membership.TenantRole), + KolProfile: kolProfile, }, }, nil } @@ -137,14 +162,41 @@ func (s *AuthService) Me(ctx context.Context, actor auth.Actor) (*UserInfo, erro return nil, response.ErrNotFound(40401, "user_not_found", "user not found") } + kolProfile, err := s.loadKolProfileBrief(ctx, actor.TenantID, actor.UserID) + if err != nil { + return nil, err + } + return &UserInfo{ - ID: actor.UserID, + ID: user.ID, Email: user.Email, Name: user.Name, AvatarURL: user.AvatarURL, TenantID: actor.TenantID, TenantRole: actor.Role, Permissions: auth.PermissionsForRole(actor.Role), + KolProfile: kolProfile, + }, nil +} + +func (s *AuthService) loadKolProfileBrief(ctx context.Context, tenantID, userID int64) (*KolProfileBrief, error) { + if s.kolProfiles == nil { + return nil, nil + } + + profile, err := s.kolProfiles.GetByUser(ctx, tenantID, userID) + if err != nil { + return nil, fmt.Errorf("get kol profile: %w", err) + } + if profile == nil || profile.Status != "active" { + return nil, nil + } + + return &KolProfileBrief{ + ID: profile.ID, + DisplayName: profile.DisplayName, + Status: profile.Status, + MarketEnabled: profile.MarketEnabled, }, nil } diff --git a/server/internal/tenant/transport/auth_handler.go b/server/internal/tenant/transport/auth_handler.go index 0fab07e..57c91c1 100644 --- a/server/internal/tenant/transport/auth_handler.go +++ b/server/internal/tenant/transport/auth_handler.go @@ -18,7 +18,12 @@ type AuthHandler struct { func NewAuthHandler(a *bootstrap.App) *AuthHandler { return &AuthHandler{ - svc: app.NewAuthService(repository.NewAuthRepository(a.DB), a.JWT, a.Sessions), + svc: app.NewAuthService( + repository.NewAuthRepository(a.DB), + a.KolProfiles, + a.JWT, + a.Sessions, + ), } } diff --git a/server/scripts/seed_kol_profile.sql b/server/scripts/seed_kol_profile.sql new file mode 100644 index 0000000..4855d4a --- /dev/null +++ b/server/scripts/seed_kol_profile.sql @@ -0,0 +1,16 @@ +-- V1 helper: insert a KOL profile manually. +-- Usage: +-- PGPASSWORD=geo_dev psql -h localhost -U geo -d geo \ +-- -v tenant_id=1 -v user_id=5 -v display_name="'测试 KOL'" -v bio="'家具行业专家'" \ +-- -f server/scripts/seed_kol_profile.sql +INSERT INTO kol_profiles (tenant_id, user_id, display_name, bio, market_enabled, status) +VALUES ( + :tenant_id::bigint, + :user_id::bigint, + :display_name, + :bio, + TRUE, + 'active' +) +ON CONFLICT DO NOTHING +RETURNING id, tenant_id, user_id, display_name, status;