Files
quyun-v2/backend/app/services/tenant.go
2026-01-08 14:44:07 +08:00

291 lines
7.3 KiB
Go

package services
import (
"context"
"errors"
"quyun/v2/app/errorx"
"quyun/v2/app/http/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"go.ipao.vip/gen/types"
"gorm.io/gorm"
)
// @provider
type tenant struct{}
func (s *tenant) List(ctx context.Context, filter *dto.TenantListFilter) (*requests.Pager, error) {
tbl, q := models.TenantQuery.QueryContext(ctx)
q = q.Where(tbl.Status.Eq(consts.TenantStatusVerified))
if filter.Keyword != nil && *filter.Keyword != "" {
q = q.Where(tbl.Name.Like("%" + *filter.Keyword + "%"))
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
tenantIDs := make([]int64, 0, len(list))
for _, t := range list {
tenantIDs = append(tenantIDs, t.ID)
}
// 批量统计关注数与内容数,避免逐条 Count。
followerMap, err := s.countFollowers(ctx, tenantIDs)
if err != nil {
return nil, err
}
contentMap, err := s.countPublishedContents(ctx, tenantIDs)
if err != nil {
return nil, err
}
var data []dto.TenantProfile
for _, t := range list {
cfg := t.Config.Data()
data = append(data, dto.TenantProfile{
ID: t.ID,
Name: t.Name,
Avatar: cfg.Avatar,
Bio: cfg.Bio,
Stats: dto.Stats{
Followers: int(followerMap[t.ID]),
Contents: int(contentMap[t.ID]),
},
})
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: data,
}, nil
}
func (s *tenant) GetPublicProfile(ctx context.Context, userID, id int64) (*dto.TenantProfile, error) {
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(id)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// Stats
followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(id)).Count()
contents, _ := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.TenantID.Eq(id), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).
Count()
// Following status
isFollowing := false
if userID > 0 {
uid := userID
isFollowing, _ = models.TenantUserQuery.WithContext(ctx).
Where(models.TenantUserQuery.TenantID.Eq(id), models.TenantUserQuery.UserID.Eq(uid)).
Exists()
}
cfg := t.Config.Data()
return &dto.TenantProfile{
ID: t.ID,
Name: t.Name,
Avatar: cfg.Avatar,
Cover: cfg.Cover,
Bio: cfg.Bio,
Description: cfg.Description,
Stats: dto.Stats{
Followers: int(followers),
Contents: int(contents),
},
IsFollowing: isFollowing,
}, nil
}
func (s *tenant) Follow(ctx context.Context, userID, id int64) error {
if userID == 0 {
return errorx.ErrUnauthorized
}
uid := userID
// Check if tenant exists
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(id)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
tu := &models.TenantUser{
TenantID: id,
UserID: uid,
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
Status: consts.UserStatusVerified,
}
if err := models.TenantUserQuery.WithContext(ctx).Save(tu); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Notification != nil {
_ = Notification.Send(ctx, t.UserID, "interaction", "新增粉丝", "有人关注了您的店铺: "+t.Name)
}
return nil
}
func (s *tenant) Unfollow(ctx context.Context, userID, id int64) error {
if userID == 0 {
return errorx.ErrUnauthorized
}
uid := userID
_, err := models.TenantUserQuery.WithContext(ctx).
Where(models.TenantUserQuery.TenantID.Eq(id), models.TenantUserQuery.UserID.Eq(uid)).
Delete()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
func (s *tenant) ListFollowed(ctx context.Context, userID int64) ([]dto.TenantProfile, error) {
if userID == 0 {
return nil, errorx.ErrUnauthorized
}
uid := userID
tbl, q := models.TenantUserQuery.QueryContext(ctx)
list, err := q.Where(tbl.UserID.Eq(uid)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if len(list) == 0 {
return []dto.TenantProfile{}, nil
}
tenantIDs := make([]int64, 0, len(list))
tenantIDSet := make(map[int64]struct{}, len(list))
for _, tu := range list {
if _, ok := tenantIDSet[tu.TenantID]; ok {
continue
}
tenantIDs = append(tenantIDs, tu.TenantID)
tenantIDSet[tu.TenantID] = struct{}{}
}
tblTenant, qTenant := models.TenantQuery.QueryContext(ctx)
tenants, err := qTenant.Where(tblTenant.ID.In(tenantIDs...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
tenantMap := make(map[int64]*models.Tenant, len(tenants))
for _, t := range tenants {
tenantMap[t.ID] = t
}
// 批量统计关注数与内容数,避免逐条 Count。
followerMap, err := s.countFollowers(ctx, tenantIDs)
if err != nil {
return nil, err
}
contentMap, err := s.countPublishedContents(ctx, tenantIDs)
if err != nil {
return nil, err
}
var data []dto.TenantProfile
for _, tu := range list {
t, ok := tenantMap[tu.TenantID]
if !ok {
continue
}
cfg := t.Config.Data()
data = append(data, dto.TenantProfile{
ID: t.ID,
Name: t.Name,
Avatar: cfg.Avatar,
Stats: dto.Stats{
Followers: int(followerMap[tu.TenantID]),
Contents: int(contentMap[tu.TenantID]),
},
IsFollowing: true,
})
}
return data, nil
}
// GetModelByID 获取指定 ID 的model
func (s *tenant) GetModelByID(ctx context.Context, id int64) (*models.Tenant, error) {
tbl, query := models.TenantQuery.QueryContext(ctx)
u, err := query.Where(tbl.ID.Eq(id)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return u, nil
}
type tenantStatRow struct {
TenantID int64 `gorm:"column:tenant_id"`
Total int64 `gorm:"column:total"`
}
func (s *tenant) countFollowers(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) {
if len(tenantIDs) == 0 {
return map[int64]int64{}, nil
}
tbl, q := models.TenantUserQuery.QueryContext(ctx)
var rows []tenantStatRow
err := q.Where(tbl.TenantID.In(tenantIDs...)).
Select(tbl.TenantID, tbl.ALL.Count().As("total")).
Group(tbl.TenantID).
Scan(&rows)
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
result := make(map[int64]int64, len(rows))
for _, row := range rows {
result[row.TenantID] = row.Total
}
return result, nil
}
func (s *tenant) countPublishedContents(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) {
if len(tenantIDs) == 0 {
return map[int64]int64{}, nil
}
tbl, q := models.ContentQuery.QueryContext(ctx)
var rows []tenantStatRow
err := q.Where(tbl.TenantID.In(tenantIDs...), tbl.Status.Eq(consts.ContentStatusPublished)).
Select(tbl.TenantID, tbl.ALL.Count().As("total")).
Group(tbl.TenantID).
Scan(&rows)
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
result := make(map[int64]int64, len(rows))
for _, row := range rows {
result[row.TenantID] = row.Total
}
return result, nil
}