298 lines
7.5 KiB
Go
298 lines
7.5 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, tenantID int64, filter *dto.TenantListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
|
q = q.Where(tbl.Status.Eq(consts.TenantStatusVerified))
|
|
if tenantID > 0 {
|
|
q = q.Where(tbl.ID.Eq(tenantID))
|
|
}
|
|
|
|
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, tenantID, userID int64) (*dto.TenantProfile, error) {
|
|
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).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(tenantID)).Count()
|
|
contents, _ := models.ContentQuery.WithContext(ctx).
|
|
Where(models.ContentQuery.TenantID.Eq(tenantID), 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(tenantID), 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, tenantID, userID 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(tenantID)).First()
|
|
if err != nil {
|
|
return errorx.ErrRecordNotFound
|
|
}
|
|
|
|
tu := &models.TenantUser{
|
|
TenantID: tenantID,
|
|
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, tenantID, t.UserID, "interaction", "新增粉丝", "有人关注了您的店铺: "+t.Name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *tenant) Unfollow(ctx context.Context, tenantID, userID int64) error {
|
|
if userID == 0 {
|
|
return errorx.ErrUnauthorized
|
|
}
|
|
uid := userID
|
|
|
|
_, err := models.TenantUserQuery.WithContext(ctx).
|
|
Where(models.TenantUserQuery.TenantID.Eq(tenantID), models.TenantUserQuery.UserID.Eq(uid)).
|
|
Delete()
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *tenant) ListFollowed(ctx context.Context, tenantID, userID int64) ([]dto.TenantProfile, error) {
|
|
if userID == 0 {
|
|
return nil, errorx.ErrUnauthorized
|
|
}
|
|
uid := userID
|
|
|
|
tbl, q := models.TenantUserQuery.QueryContext(ctx)
|
|
q = q.Where(tbl.UserID.Eq(uid))
|
|
if tenantID > 0 {
|
|
q = q.Where(tbl.TenantID.Eq(tenantID))
|
|
}
|
|
list, err := q.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
|
|
}
|