feat(kol): expose kol_profile on /api/auth/me and add seed script
This commit is contained in:
@@ -30,6 +30,7 @@ export interface UserInfo {
|
|||||||
tenant_id: number;
|
tenant_id: number;
|
||||||
tenant_role: string;
|
tenant_role: string;
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
|
kol_profile: KolProfileBrief | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/geo-platform/tenant-api/internal/shared/retrieval"
|
"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/shared/stream"
|
||||||
"github.com/geo-platform/tenant-api/internal/tenant/prompts"
|
"github.com/geo-platform/tenant-api/internal/tenant/prompts"
|
||||||
|
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
@@ -45,6 +46,7 @@ type App struct {
|
|||||||
AuditLogs *auditlog.AsyncWriter
|
AuditLogs *auditlog.AsyncWriter
|
||||||
GenerationStreams *stream.GenerationHub
|
GenerationStreams *stream.GenerationHub
|
||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
|
KolProfiles repository.KolProfileRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(configPath string) (*App, error) {
|
func New(configPath string) (*App, error) {
|
||||||
@@ -96,6 +98,7 @@ func New(configPath string) (*App, error) {
|
|||||||
auditLogs := auditlog.NewAsyncWriter(pool, logger)
|
auditLogs := auditlog.NewAsyncWriter(pool, logger)
|
||||||
generationStreams := stream.NewGenerationHub(mqClient)
|
generationStreams := stream.NewGenerationHub(mqClient)
|
||||||
appCache := cache.New(cfg.Cache.Driver, rdb)
|
appCache := cache.New(cfg.Cache.Driver, rdb)
|
||||||
|
kolProfiles := repository.NewKolProfileRepository(pool)
|
||||||
|
|
||||||
if cfg.Server.Mode == "release" {
|
if cfg.Server.Mode == "release" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -149,6 +152,7 @@ func New(configPath string) (*App, error) {
|
|||||||
AuditLogs: auditLogs,
|
AuditLogs: auditLogs,
|
||||||
GenerationStreams: generationStreams,
|
GenerationStreams: generationStreams,
|
||||||
Cache: appCache,
|
Cache: appCache,
|
||||||
|
KolProfiles: kolProfiles,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
repo repository.AuthRepository
|
repo repository.AuthRepository
|
||||||
jwt *auth.Manager
|
kolProfiles repository.KolProfileRepository
|
||||||
sessions *auth.SessionStore
|
jwt *auth.Manager
|
||||||
|
sessions *auth.SessionStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService(repo repository.AuthRepository, jwt *auth.Manager, sessions *auth.SessionStore) *AuthService {
|
func NewAuthService(
|
||||||
return &AuthService{repo: repo, jwt: jwt, sessions: sessions}
|
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 {
|
type LoginRequest struct {
|
||||||
@@ -35,13 +46,21 @@ type LoginResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AvatarURL *string `json:"avatar_url"`
|
AvatarURL *string `json:"avatar_url"`
|
||||||
TenantID int64 `json:"tenant_id"`
|
TenantID int64 `json:"tenant_id"`
|
||||||
TenantRole string `json:"tenant_role"`
|
TenantRole string `json:"tenant_role"`
|
||||||
Permissions []string `json:"permissions"`
|
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) {
|
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)
|
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{
|
return &LoginResponse{
|
||||||
AccessToken: pair.AccessToken,
|
AccessToken: pair.AccessToken,
|
||||||
RefreshToken: pair.RefreshToken,
|
RefreshToken: pair.RefreshToken,
|
||||||
@@ -81,6 +105,7 @@ func (s *AuthService) Login(ctx context.Context, req LoginRequest) (*LoginRespon
|
|||||||
TenantID: membership.TenantID,
|
TenantID: membership.TenantID,
|
||||||
TenantRole: membership.TenantRole,
|
TenantRole: membership.TenantRole,
|
||||||
Permissions: auth.PermissionsForRole(membership.TenantRole),
|
Permissions: auth.PermissionsForRole(membership.TenantRole),
|
||||||
|
KolProfile: kolProfile,
|
||||||
},
|
},
|
||||||
}, nil
|
}, 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")
|
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{
|
return &UserInfo{
|
||||||
ID: actor.UserID,
|
ID: user.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
AvatarURL: user.AvatarURL,
|
AvatarURL: user.AvatarURL,
|
||||||
TenantID: actor.TenantID,
|
TenantID: actor.TenantID,
|
||||||
TenantRole: actor.Role,
|
TenantRole: actor.Role,
|
||||||
Permissions: auth.PermissionsForRole(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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ type AuthHandler struct {
|
|||||||
|
|
||||||
func NewAuthHandler(a *bootstrap.App) *AuthHandler {
|
func NewAuthHandler(a *bootstrap.App) *AuthHandler {
|
||||||
return &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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user