Scope knowledge and image libraries by brand
Frontend CI / Frontend (push) Successful in 2m59s
Backend CI / Backend (push) Successful in 14m58s

This commit is contained in:
2026-06-30 12:30:10 +08:00
parent 95ddb3057f
commit ced0c4ec0f
35 changed files with 1136 additions and 326 deletions
@@ -10,6 +10,7 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
"github.com/geo-platform/tenant-api/internal/shared/auth"
"github.com/geo-platform/tenant-api/internal/shared/messaging/rabbitmq"
"github.com/geo-platform/tenant-api/internal/shared/middleware"
"github.com/geo-platform/tenant-api/internal/shared/objectstorage"
@@ -48,9 +49,13 @@ type ImageReferenceArticleBrief struct {
func (s *ImageFolderService) List(ctx context.Context) ([]ImageFolderItem, error) {
tenantID := middleware.TenantIDFromCtx(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return nil, err
}
repo := repository.NewImageFolderRepository(s.pool)
folders, err := repo.List(ctx, tenantID)
folders, err := repo.List(ctx, tenantID, brandID)
if err != nil {
return nil, err
}
@@ -77,11 +82,15 @@ func (s *ImageFolderService) Create(ctx context.Context, name string) (int64, er
}
tenantID := middleware.TenantIDFromCtx(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return 0, err
}
repo := repository.NewImageFolderRepository(s.pool)
id, err := repo.Create(ctx, tenantID, name)
id, err := repo.Create(ctx, tenantID, brandID, name)
if err != nil {
if isConstraintError(err, "uq_image_folders_tenant_name") {
if isConstraintError(err, "uq_image_folders_tenant_name") || isConstraintError(err, "uq_image_folders_tenant_brand_name") {
return 0, response.ErrConflict(40901, "folder_name_exists", "a folder with this name already exists")
}
return 0, err
@@ -96,13 +105,17 @@ func (s *ImageFolderService) Update(ctx context.Context, id int64, name string)
}
tenantID := middleware.TenantIDFromCtx(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return err
}
repo := repository.NewImageFolderRepository(s.pool)
if _, err := repo.Get(ctx, tenantID, id); err != nil {
if _, err := repo.Get(ctx, tenantID, brandID, id); err != nil {
return mapImageFolderNotFound(err)
}
if err := repo.Update(ctx, tenantID, id, name); err != nil {
if isConstraintError(err, "uq_image_folders_tenant_name") {
if err := repo.Update(ctx, tenantID, brandID, id, name); err != nil {
if isConstraintError(err, "uq_image_folders_tenant_name") || isConstraintError(err, "uq_image_folders_tenant_brand_name") {
return response.ErrConflict(40901, "folder_name_exists", "a folder with this name already exists")
}
return err
@@ -112,13 +125,17 @@ func (s *ImageFolderService) Update(ctx context.Context, id int64, name string)
func (s *ImageFolderService) DeletePreview(ctx context.Context, folderID int64) (*FolderDeletePreview, error) {
tenantID := middleware.TenantIDFromCtx(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return nil, err
}
folderRepo := repository.NewImageFolderRepository(s.pool)
if _, err := folderRepo.Get(ctx, tenantID, folderID); err != nil {
if _, err := folderRepo.Get(ctx, tenantID, brandID, folderID); err != nil {
return nil, mapImageFolderNotFound(err)
}
imageCount, err := folderRepo.CountActiveImages(ctx, tenantID, folderID)
imageCount, err := folderRepo.CountActiveImages(ctx, tenantID, brandID, folderID)
if err != nil {
return nil, err
}
@@ -131,19 +148,23 @@ func (s *ImageFolderService) DeletePreview(ctx context.Context, folderID int64)
func (s *ImageFolderService) Delete(ctx context.Context, folderID int64, force bool) error {
tenantID := middleware.TenantIDFromCtx(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return err
}
folderRepo := repository.NewImageFolderRepository(s.pool)
imageRepo := repository.NewImageRepository(s.pool)
if _, err := folderRepo.Get(ctx, tenantID, folderID); err != nil {
if _, err := folderRepo.Get(ctx, tenantID, brandID, folderID); err != nil {
return mapImageFolderNotFound(err)
}
imageCount, err := folderRepo.CountActiveImages(ctx, tenantID, folderID)
imageCount, err := folderRepo.CountActiveImages(ctx, tenantID, brandID, folderID)
if err != nil {
return err
}
if imageCount == 0 {
return folderRepo.Delete(ctx, tenantID, folderID)
return folderRepo.Delete(ctx, tenantID, brandID, folderID)
}
if !force {
@@ -159,7 +180,7 @@ func (s *ImageFolderService) Delete(ctx context.Context, folderID int64, force b
}
}
images, err := imageRepo.ListActiveByFolder(ctx, tenantID, folderID)
images, err := imageRepo.ListActiveByFolder(ctx, tenantID, brandID, folderID)
if err != nil {
return err
}
@@ -174,10 +195,10 @@ func (s *ImageFolderService) Delete(ctx context.Context, folderID int64, force b
imageRepoTx := repository.NewImageRepository(tx)
folderRepoTx := repository.NewImageFolderRepository(tx)
if err := imageRepoTx.MarkImagesInFolderPendingDelete(ctx, tenantID, folderID); err != nil {
if err := imageRepoTx.MarkImagesInFolderPendingDelete(ctx, tenantID, brandID, folderID); err != nil {
return response.ErrInternal(50048, "folder_image_delete_prepare_failed", "failed to prepare folder images for deletion")
}
if err := folderRepoTx.Delete(ctx, tenantID, folderID); err != nil {
if err := folderRepoTx.Delete(ctx, tenantID, brandID, folderID); err != nil {
return err
}
if err := tx.Commit(ctx); err != nil {
@@ -185,7 +206,8 @@ func (s *ImageFolderService) Delete(ctx context.Context, folderID int64, force b
}
for _, image := range images {
if err := s.imageSvc.finalizeDeleteNowOrEnqueue(ctx, tenantID, image.ID); err != nil {
deleteCtx := auth.WithCurrentBrandID(ctx, brandID)
if err := s.imageSvc.finalizeDeleteNowOrEnqueue(deleteCtx, tenantID, image.ID); err != nil {
return err
}
}