This commit is contained in:
toom1996
2026-10-18 17:42:10 +08:00
parent 1b06bd5bda
commit 1de1ec46a6
58 changed files with 5281 additions and 19191 deletions

View File

@ -91,8 +91,13 @@ func NewBackstage(opt BackstageOptions) *gin.Engine {
admin.POST("/brands", opt.Backstage.BrandCreate)
admin.GET("/brands/:id", opt.Backstage.BrandEdit)
admin.POST("/brands/:id", opt.Backstage.BrandUpdate)
// 软删品牌(is_deleted=1)。有未删除图集时 service 会拒绝,不做隐式级联删除。
admin.POST("/brands/:id/delete", opt.Backstage.BrandDelete)
// 文章管理:走秀 + 街拍混合列表,替代原「审核页 + 分类列表」,是编辑/通过/删除(下架)的统一入口。
admin.GET("/articles", opt.Backstage.Articles)
// 手工新建文章:表单 + 提交(multipart:元数据 + 多张图片,一步到位)。
admin.GET("/articles/new", opt.Backstage.ArticleNew)
admin.POST("/articles", opt.Backstage.ArticleCreate)
admin.POST("/articles/batch", opt.Backstage.ArticleBatch)
admin.POST("/articles/:kind/:id/approve", opt.Backstage.ArticleApprove)
// 街拍主副图分组(零 JS:同步表单 POST,成功 302 回编辑页;AJAX 版见路由上方)。

View File

@ -75,6 +75,12 @@ func (f *fakeBrand) Update(ctx context.Context, uid, nameEn, nameCn string) erro
}
return nil
}
func (f *fakeBrand) Delete(ctx context.Context, uid string) error {
if uid != "001DESke" {
return service.ErrBrandNotFound
}
return nil
}
func (f *fakeBrand) CrawlTasks(ctx context.Context, brandID uint32) ([]dto.CrawlBrand, error) {
return []dto.CrawlBrand{{BrandUID: "001DESke", Name: "Acne Studios"}}, nil
}
@ -147,9 +153,6 @@ func (f *fakeArticle) SetDeleted(ctx context.Context, id uint32, deleted uint8)
func (f *fakeArticle) PurgeGallery(ctx context.Context, id uint32) error {
return nil
}
func (f *fakeArticle) GetRunwayDupSources(ctx context.Context, imageIDs []uint32) (map[uint32]dto.DupSource, error) {
return map[uint32]dto.DupSource{}, nil
}
func (f *fakeArticle) SumImageSize(ctx context.Context, runwayID uint32) (int64, error) {
return 0, nil
}
@ -210,9 +213,6 @@ func (f *fakeStreet) SetDeleted(ctx context.Context, id uint32, deleted uint8) e
func (f *fakeStreet) PurgeGallery(ctx context.Context, id uint32) error {
return nil
}
func (f *fakeStreet) GetStreetSnapDupSources(ctx context.Context, imageIDs []uint32) (map[uint32]dto.DupSource, error) {
return map[uint32]dto.DupSource{}, nil
}
func (f *fakeStreet) SumImageSize(ctx context.Context, snapID uint32) (int64, error) {
return 0, nil
}
@ -220,8 +220,8 @@ func (f *fakeStreet) SumImageSize(ctx context.Context, snapID uint32) (int64, er
// fakeAdminRepo 实现文章管理仓储接口;返回固定的混合列表示例,供路由/模板渲染测试。
type fakeAdminRepo struct{}
func (f *fakeAdminRepo) ImageSizeByRecords(ctx context.Context, kind string, ids []uint32) (map[uint32]int64, error) {
return map[uint32]int64{}, nil
func (f *fakeAdminRepo) ImageSizeByRecords(ctx context.Context, kind string, ids []uint32) (map[uint32]repository.AdminRecordStats, error) {
return map[uint32]repository.AdminRecordStats{}, nil
}
func (f *fakeAdminRepo) ListMixed(_ context.Context, status, kind string, page, size int) ([]repository.AdminRecordCard, int64, error) {
@ -270,17 +270,13 @@ func (f *fakeAdminRepo) CountMixed(_ context.Context, status, kind string) (int6
}
return 0, nil
}
func (f *fakeAdminRepo) DupRateByRecords(_ context.Context, kind string, ids []uint32) (map[uint32]repository.DupStat, error) {
return map[uint32]repository.DupStat{}, nil
}
// ── 冒烟测试:验证路由 / 鉴权 / 模板渲染(无需真实数据库)─────────────────
func TestBackstage(t *testing.T) {
m := jwt.NewManager("test-secret", 168)
auth := &fakeAuth{jwt: m}
brand := &fakeBrand{}
h := handler.NewBackstageHandler(auth, brand, &fakeArticle{}, &fakeStreet{}, &fakeReview{attachOK: true}, nil, &fakeAdminRepo{}, nil)
h := handler.NewBackstageHandler(auth, brand, &fakeArticle{}, &fakeStreet{}, &fakeReview{attachOK: true}, nil, &fakeAdminRepo{}, nil, nil)
r := NewBackstage(BackstageOptions{Config: &config.Config{}, JWT: m, Backstage: h})
// 1) 登录页可访问
@ -488,7 +484,7 @@ func newReviewRouter(t *testing.T) (*gin.Engine, *fakeReview) {
auth := &fakeAuth{jwt: m}
brand := &fakeBrand{}
rev := &fakeReview{attachOK: true}
h := handler.NewBackstageHandler(auth, brand, &fakeArticle{}, &fakeStreet{}, rev, nil, &fakeAdminRepo{}, nil)
h := handler.NewBackstageHandler(auth, brand, &fakeArticle{}, &fakeStreet{}, rev, nil, &fakeAdminRepo{}, nil, nil)
r := NewBackstage(BackstageOptions{Config: &config.Config{}, JWT: m, Backstage: h})
return r, rev
}