update
This commit is contained in:
@ -3,6 +3,8 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fashionapi/internal/dto"
|
||||
"fashionapi/internal/model"
|
||||
@ -14,12 +16,28 @@ import (
|
||||
type ArticleRepository interface {
|
||||
// List 按筛选条件分页查询文章,同时返回符合条件的总数。
|
||||
List(ctx context.Context, q dto.ArticleQuery) ([]model.RunwayRow, int64, error)
|
||||
// ListAdmin 后台管理列表:投影列含 season/year/collection_type/season_code/source_url 等管理字段。
|
||||
ListAdmin(ctx context.Context, q dto.ArticleQuery) ([]model.RunwayRow, int64, error)
|
||||
// FindByID 查询单篇文章(含 JOIN 出的品牌名)。不存在时返回 ErrNotFound。
|
||||
FindByID(ctx context.Context, id string) (*model.RunwayRow, error)
|
||||
// ListImages 查询某篇文章的全部图片,按排序值升序。
|
||||
ListImages(ctx context.Context, runwayID string) ([]model.BrandRunwayImage, error)
|
||||
// ImagesByRunwayIDs 批量查询多篇文章的图片,单次 IN 查询避免 N+1。
|
||||
ImagesByRunwayIDs(ctx context.Context, ids []uint32) (map[uint32][]model.BrandRunwayImage, error)
|
||||
// GetForEdit 取原始走秀记录(含全部 en/cn 字段)与图片,供后台编辑表单预填。
|
||||
GetForEdit(ctx context.Context, id uint32) (*model.BrandRunway, []model.BrandRunwayImage, error)
|
||||
// UpdateRunway 审核/编辑后更新字段(白名单,杜绝任意列注入)。
|
||||
UpdateRunway(ctx context.Context, id uint32, fields map[string]any) error
|
||||
// DeleteRunwayImage 软删单张走秀图片。
|
||||
DeleteRunwayImage(ctx context.Context, imageID uint32) error
|
||||
// GetRunwayImageKey 取单张走秀图片的七牛 key(删除前清理用);不存在返回空串。
|
||||
GetRunwayImageKey(ctx context.Context, imageID uint32) (string, error)
|
||||
// SoftDeleteRunwayImages 软删某走秀的全部图片(级联下架用),返回被删图片的 key 列表。
|
||||
SoftDeleteRunwayImages(ctx context.Context, runwayID uint32) ([]string, error)
|
||||
// SetRunwayCover 更新走秀封面(同时回到图片列表首张)。
|
||||
SetRunwayCover(ctx context.Context, runwayID uint32, image string) error
|
||||
// SetRunwayDeleted 上下架:deleted=1 下架(对外不可见),0 上架。
|
||||
SetRunwayDeleted(ctx context.Context, id uint32, deleted uint8) error
|
||||
}
|
||||
|
||||
type articleRepository struct {
|
||||
@ -43,6 +61,15 @@ const articleDetailColumns = `brand_runway.id, brand_runway.title_en, brand_runw
|
||||
brand_runway.description_en, brand_runway.description_cn,
|
||||
brand_runway.cover, b.name_en AS brand_name_en, b.name_cn AS brand_name_cn`
|
||||
|
||||
// articleAdminColumns 后台管理列表投影列:在列表列基础上补回 season / year /
|
||||
// collection_type / season_code / source_url 等管理字段(前端列表不展示这些)。
|
||||
const articleAdminColumns = `brand_runway.id, brand_runway.brand_id,
|
||||
brand_runway.title_en, brand_runway.title_cn,
|
||||
brand_runway.cover, brand_runway.image_count, brand_runway.year,
|
||||
brand_runway.collection_type, brand_runway.season, brand_runway.season_code,
|
||||
brand_runway.source_url,
|
||||
b.name_en AS brand_name_en, b.name_cn AS brand_name_cn`
|
||||
|
||||
// brandJoin 关联品牌表取品牌名;LEFT JOIN 保证品牌被软删时文章依然可见。
|
||||
const brandJoin = "LEFT JOIN brand b ON b.id = brand_runway.brand_id AND b.is_deleted = 0"
|
||||
|
||||
@ -140,6 +167,36 @@ func (r *articleRepository) List(ctx context.Context, q dto.ArticleQuery) ([]mod
|
||||
return rows, total, nil
|
||||
}
|
||||
|
||||
// ListAdmin 后台管理列表:与 List 共用筛选/排序,但投影列含管理字段。
|
||||
func (r *articleRepository) ListAdmin(ctx context.Context, q dto.ArticleQuery) ([]model.RunwayRow, int64, error) {
|
||||
scope := filterScope(q)
|
||||
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Scopes(scope).
|
||||
Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if total == 0 {
|
||||
return []model.RunwayRow{}, 0, nil
|
||||
}
|
||||
|
||||
var rows []model.RunwayRow
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Scopes(scope).
|
||||
Select(articleAdminColumns).
|
||||
Joins(brandJoin).
|
||||
Order(orderBy(q.Sort)).
|
||||
Offset(q.Offset()).
|
||||
Limit(q.Size).
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return rows, total, nil
|
||||
}
|
||||
|
||||
func (r *articleRepository) FindByID(ctx context.Context, id string) (*model.RunwayRow, error) {
|
||||
var row model.RunwayRow
|
||||
err := r.db.WithContext(ctx).
|
||||
@ -187,4 +244,106 @@ func (r *articleRepository) ImagesByRunwayIDs(ctx context.Context, ids []uint32)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IDs 返回全部未删除文章的 id(升序),供 SSG 构建期枚举详情页路径。
|
||||
// ── 后台编辑支持 ──────────────────────────────────────────────────────────────
|
||||
|
||||
// runwayEditable 后台可编辑的走秀字段白名单(键即列名)。
|
||||
var runwayEditable = map[string]bool{
|
||||
"title_en": true,
|
||||
"title_cn": true,
|
||||
"description_en": true,
|
||||
"description_cn": true,
|
||||
"year": true,
|
||||
"season": true,
|
||||
"collection_type": true,
|
||||
"season_code": true,
|
||||
}
|
||||
|
||||
func (r *articleRepository) GetForEdit(ctx context.Context, id uint32) (*model.BrandRunway, []model.BrandRunwayImage, error) {
|
||||
var rw model.BrandRunway
|
||||
if err := r.db.WithContext(ctx).Where("id = ? AND is_deleted = 0", id).First(&rw).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, ErrNotFound
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
imgs, err := r.ListImages(ctx, strconv.FormatUint(uint64(id), 10))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &rw, imgs, nil
|
||||
}
|
||||
|
||||
func (r *articleRepository) UpdateRunway(ctx context.Context, id uint32, fields map[string]any) error {
|
||||
clean := map[string]any{}
|
||||
for k, v := range fields {
|
||||
if runwayEditable[k] {
|
||||
clean[k] = v
|
||||
}
|
||||
}
|
||||
if len(clean) == 0 {
|
||||
return nil
|
||||
}
|
||||
clean["updated_at"] = uint32(time.Now().Unix())
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Where("id = ?", id).
|
||||
Updates(clean).Error
|
||||
}
|
||||
|
||||
func (r *articleRepository) DeleteRunwayImage(ctx context.Context, imageID uint32) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunwayImage{}).
|
||||
Where("id = ? AND is_deleted = 0", imageID).
|
||||
Updates(map[string]any{"is_deleted": 1, "updated_at": uint32(time.Now().Unix())}).Error
|
||||
}
|
||||
|
||||
func (r *articleRepository) GetRunwayImageKey(ctx context.Context, imageID uint32) (string, error) {
|
||||
var img model.BrandRunwayImage
|
||||
err := r.db.WithContext(ctx).Where("id = ?", imageID).First(&img).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return img.Image, nil
|
||||
}
|
||||
|
||||
// SoftDeleteRunwayImages 软删某走秀的全部图片(级联下架用)。
|
||||
// 先取非删除图片的 key 列表返回给调用方(用于后续七牛孤儿清理),再统一置 is_deleted=1。
|
||||
func (r *articleRepository) SoftDeleteRunwayImages(ctx context.Context, runwayID uint32) ([]string, error) {
|
||||
var imgs []model.BrandRunwayImage
|
||||
if err := r.db.WithContext(ctx).
|
||||
Select("image").
|
||||
Where("runway_id = ? AND is_deleted = 0", runwayID).
|
||||
Find(&imgs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, 0, len(imgs))
|
||||
for _, im := range imgs {
|
||||
if im.Image != "" {
|
||||
keys = append(keys, im.Image)
|
||||
}
|
||||
}
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunwayImage{}).
|
||||
Where("runway_id = ? AND is_deleted = 0", runwayID).
|
||||
Updates(map[string]any{"is_deleted": 1, "updated_at": uint32(time.Now().Unix())}).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (r *articleRepository) SetRunwayCover(ctx context.Context, runwayID uint32, image string) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Where("id = ?", runwayID).
|
||||
Updates(map[string]any{"cover": image, "updated_at": uint32(time.Now().Unix())}).Error
|
||||
}
|
||||
|
||||
func (r *articleRepository) SetRunwayDeleted(ctx context.Context, id uint32, deleted uint8) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{"is_deleted": deleted, "updated_at": uint32(time.Now().Unix())}).Error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user