feat: tenant-scoped routing and portal navigation

This commit is contained in:
2026-01-08 21:30:46 +08:00
parent f3aa92078a
commit 3e095c57f3
52 changed files with 1111 additions and 670 deletions

View File

@@ -18,11 +18,14 @@ import (
// @provider
type content struct{}
func (s *content) List(ctx context.Context, filter *content_dto.ContentListFilter) (*requests.Pager, error) {
func (s *content) List(ctx context.Context, tenantID int64, filter *content_dto.ContentListFilter) (*requests.Pager, error) {
tbl, q := models.ContentQuery.QueryContext(ctx)
// Filters
q = q.Where(tbl.Status.Eq(consts.ContentStatusPublished))
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
if filter.Keyword != nil && *filter.Keyword != "" {
keyword := "%" + *filter.Keyword + "%"
q = q.Where(tbl.Title.Like(keyword)).Or(tbl.Description.Like(keyword))
@@ -31,6 +34,9 @@ func (s *content) List(ctx context.Context, filter *content_dto.ContentListFilte
q = q.Where(tbl.Genre.Eq(*filter.Genre))
}
if filter.TenantID != nil && *filter.TenantID > 0 {
if tenantID > 0 && *filter.TenantID != tenantID {
return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
}
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.IsPinned != nil {
@@ -128,16 +134,22 @@ func (s *content) List(ctx context.Context, filter *content_dto.ContentListFilte
}, nil
}
func (s *content) Get(ctx context.Context, userID, id int64) (*content_dto.ContentDetail, error) {
func (s *content) Get(ctx context.Context, tenantID, userID, id int64) (*content_dto.ContentDetail, error) {
// Increment Views
_, _ = models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.ID.Eq(id)).
UpdateSimple(models.ContentQuery.Views.Add(1))
update := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id))
if tenantID > 0 {
update = update.Where(models.ContentQuery.TenantID.Eq(tenantID))
}
_, _ = update.UpdateSimple(models.ContentQuery.Views.Add(1))
_, q := models.ContentQuery.QueryContext(ctx)
var item models.Content
err := q.UnderlyingDB().
db := q.UnderlyingDB()
if tenantID > 0 {
db = db.Where("tenant_id = ?", tenantID)
}
err := db.
Preload("Author").
Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
return db.Order("sort ASC")
@@ -232,10 +244,25 @@ func (s *content) Get(ctx context.Context, userID, id int64) (*content_dto.Conte
return detail, nil
}
func (s *content) ListComments(ctx context.Context, userID, id int64, page int) (*requests.Pager, error) {
func (s *content) ListComments(ctx context.Context, tenantID, userID, id int64, page int) (*requests.Pager, error) {
if tenantID > 0 {
_, err := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.ID.Eq(id), models.ContentQuery.TenantID.Eq(tenantID)).
First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
}
tbl, q := models.CommentQuery.QueryContext(ctx)
q = q.Where(tbl.ContentID.Eq(id)).Preload(tbl.User)
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
q = q.Order(tbl.CreatedAt.Desc())
p := requests.Pagination{Page: int64(page), Limit: 10}
@@ -291,6 +318,7 @@ func (s *content) ListComments(ctx context.Context, userID, id int64, page int)
func (s *content) CreateComment(
ctx context.Context,
tenantID int64,
userID int64,
id int64,
form *content_dto.CommentCreateForm,
@@ -300,7 +328,11 @@ func (s *content) CreateComment(
}
uid := userID
c, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id)).First()
query := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id))
if tenantID > 0 {
query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
}
c, err := query.First()
if err != nil {
return errorx.ErrRecordNotFound
}
@@ -319,14 +351,18 @@ func (s *content) CreateComment(
return nil
}
func (s *content) LikeComment(ctx context.Context, userID, id int64) error {
func (s *content) LikeComment(ctx context.Context, tenantID, userID, id int64) error {
if userID == 0 {
return errorx.ErrUnauthorized
}
uid := userID
// Fetch comment for author
cm, err := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ID.Eq(id)).First()
query := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ID.Eq(id))
if tenantID > 0 {
query = query.Where(models.CommentQuery.TenantID.Eq(tenantID))
}
cm, err := query.First()
if err != nil {
return errorx.ErrRecordNotFound
}
@@ -357,14 +393,18 @@ func (s *content) LikeComment(ctx context.Context, userID, id int64) error {
return nil
}
func (s *content) GetLibrary(ctx context.Context, userID int64) ([]user_dto.ContentItem, error) {
func (s *content) GetLibrary(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
if userID == 0 {
return nil, errorx.ErrUnauthorized
}
uid := userID
tbl, q := models.ContentAccessQuery.QueryContext(ctx)
accessList, err := q.Where(tbl.UserID.Eq(uid), tbl.Status.Eq(consts.ContentAccessStatusActive)).Find()
q = q.Where(tbl.UserID.Eq(uid), tbl.Status.Eq(consts.ContentAccessStatusActive))
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
accessList, err := q.Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
@@ -380,7 +420,11 @@ func (s *content) GetLibrary(ctx context.Context, userID int64) ([]user_dto.Cont
ctbl, cq := models.ContentQuery.QueryContext(ctx)
var list []*models.Content
err = cq.Where(ctbl.ID.In(contentIDs...)).
cq = cq.Where(ctbl.ID.In(contentIDs...))
if tenantID > 0 {
cq = cq.Where(ctbl.TenantID.Eq(tenantID))
}
err = cq.
UnderlyingDB().
Preload("Author").
Preload("ContentAssets.Asset").
@@ -398,36 +442,40 @@ func (s *content) GetLibrary(ctx context.Context, userID int64) ([]user_dto.Cont
return data, nil
}
func (s *content) GetFavorites(ctx context.Context, userID int64) ([]user_dto.ContentItem, error) {
return s.getInteractList(ctx, userID, "favorite")
func (s *content) GetFavorites(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
return s.getInteractList(ctx, tenantID, userID, "favorite")
}
func (s *content) AddFavorite(ctx context.Context, userID, contentId int64) error {
return s.addInteract(ctx, userID, contentId, "favorite")
func (s *content) AddFavorite(ctx context.Context, tenantID, userID, contentId int64) error {
return s.addInteract(ctx, tenantID, userID, contentId, "favorite")
}
func (s *content) RemoveFavorite(ctx context.Context, userID, contentId int64) error {
return s.removeInteract(ctx, userID, contentId, "favorite")
func (s *content) RemoveFavorite(ctx context.Context, tenantID, userID, contentId int64) error {
return s.removeInteract(ctx, tenantID, userID, contentId, "favorite")
}
func (s *content) GetLikes(ctx context.Context, userID int64) ([]user_dto.ContentItem, error) {
return s.getInteractList(ctx, userID, "like")
func (s *content) GetLikes(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
return s.getInteractList(ctx, tenantID, userID, "like")
}
func (s *content) AddLike(ctx context.Context, userID, contentId int64) error {
return s.addInteract(ctx, userID, contentId, "like")
func (s *content) AddLike(ctx context.Context, tenantID, userID, contentId int64) error {
return s.addInteract(ctx, tenantID, userID, contentId, "like")
}
func (s *content) RemoveLike(ctx context.Context, userID, contentId int64) error {
return s.removeInteract(ctx, userID, contentId, "like")
func (s *content) RemoveLike(ctx context.Context, tenantID, userID, contentId int64) error {
return s.removeInteract(ctx, tenantID, userID, contentId, "like")
}
func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) {
func (s *content) ListTopics(ctx context.Context, tenantID int64) ([]content_dto.Topic, error) {
var results []struct {
Genre string
Count int
}
err := models.ContentQuery.WithContext(ctx).UnderlyingDB().
db := models.ContentQuery.WithContext(ctx).UnderlyingDB()
if tenantID > 0 {
db = db.Where("tenant_id = ?", tenantID)
}
err := db.
Model(&models.Content{}).
Where("status = ?", consts.ContentStatusPublished).
Select("genre, count(*) as count").
@@ -445,8 +493,12 @@ func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) {
// Fetch latest content in this genre to get a cover
var c models.Content
models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.Genre.Eq(r.Genre), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).
query := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.Genre.Eq(r.Genre), models.ContentQuery.Status.Eq(consts.ContentStatusPublished))
if tenantID > 0 {
query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
}
query.
Order(models.ContentQuery.PublishedAt.Desc()).
UnderlyingDB().
Preload("ContentAssets").
@@ -554,15 +606,19 @@ func (s *content) toMediaURLs(assets []*models.ContentAsset) []content_dto.Media
return urls
}
func (s *content) addInteract(ctx context.Context, userID, contentId int64, typ string) error {
func (s *content) addInteract(ctx context.Context, tenantID, userID, contentId int64, typ string) error {
if userID == 0 {
return errorx.ErrUnauthorized
}
uid := userID
// Fetch content for author
c, err := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.ID.Eq(contentId)).
query := models.ContentQuery.WithContext(ctx).
Where(models.ContentQuery.ID.Eq(contentId))
if tenantID > 0 {
query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
}
c, err := query.
Select(models.ContentQuery.UserID, models.ContentQuery.Title).
First()
if err != nil {
@@ -583,7 +639,11 @@ func (s *content) addInteract(ctx context.Context, userID, contentId int64, typ
}
if typ == "like" {
_, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId)).UpdateSimple(tx.Content.Likes.Add(1))
contentQuery := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId))
if tenantID > 0 {
contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
}
_, err := contentQuery.UpdateSimple(tx.Content.Likes.Add(1))
return err
}
return nil
@@ -605,7 +665,7 @@ func (s *content) addInteract(ctx context.Context, userID, contentId int64, typ
return nil
}
func (s *content) removeInteract(ctx context.Context, userID, contentId int64, typ string) error {
func (s *content) removeInteract(ctx context.Context, tenantID, userID, contentId int64, typ string) error {
if userID == 0 {
return errorx.ErrUnauthorized
}
@@ -623,14 +683,18 @@ func (s *content) removeInteract(ctx context.Context, userID, contentId int64, t
}
if typ == "like" {
_, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId)).UpdateSimple(tx.Content.Likes.Sub(1))
contentQuery := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId))
if tenantID > 0 {
contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
}
_, err := contentQuery.UpdateSimple(tx.Content.Likes.Sub(1))
return err
}
return nil
})
}
func (s *content) getInteractList(ctx context.Context, userID int64, typ string) ([]user_dto.ContentItem, error) {
func (s *content) getInteractList(ctx context.Context, tenantID, userID int64, typ string) ([]user_dto.ContentItem, error) {
if userID == 0 {
return nil, errorx.ErrUnauthorized
}
@@ -653,7 +717,11 @@ func (s *content) getInteractList(ctx context.Context, userID int64, typ string)
ctbl, cq := models.ContentQuery.QueryContext(ctx)
var list []*models.Content
err = cq.Where(ctbl.ID.In(contentIDs...)).
cq = cq.Where(ctbl.ID.In(contentIDs...))
if tenantID > 0 {
cq = cq.Where(ctbl.TenantID.Eq(tenantID))
}
err = cq.
UnderlyingDB().
Preload("Author").
Preload("ContentAssets.Asset").