Files
ttstd-infinite-canvas/backend/internal/repository/asset_library.go

214 lines
6.9 KiB
Go

package repository
import (
"encoding/json"
"errors"
"strings"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
)
type UserAssetPageFilter struct {
Kind string
Category string
FolderID *string
Uncategorized bool
Status string
Query string
}
type UserAssetFacetRow struct {
Key string
Count int64
}
func (r *Repository) UserAssetsPage(userID string, page int, pageSize int, filter UserAssetPageFilter) ([]model.Asset, int64, error) {
var assets []model.Asset
var total int64
// 角色卡是用户素材的一种,和图片、音频一起出现在素材库;列表与 facets 使用同一查询口径。
query := userAssetFilteredQuery(r.db.Model(&model.Asset{}).Where("user_id = ?", userID), filter, true)
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
err := query.Order("updated_at desc, id desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&assets).Error
return assets, total, err
}
func (r *Repository) UserAssetFacets(userID string, status string) ([]UserAssetFacetRow, []UserAssetFacetRow, []UserAssetFacetRow, error) {
base := func() *gorm.DB {
return userAssetFilteredQuery(r.db.Model(&model.Asset{}).Where("user_id = ?", userID), UserAssetPageFilter{Status: status}, false)
}
var kindRows []UserAssetFacetRow
if err := base().Select("kind AS key, COUNT(*) AS count").Group("kind").Scan(&kindRows).Error; err != nil {
return nil, nil, nil, err
}
var categoryRows []UserAssetFacetRow
if err := base().Select("category AS key, COUNT(*) AS count").Group("category").Scan(&categoryRows).Error; err != nil {
return nil, nil, nil, err
}
var folderRows []UserAssetFacetRow
if err := base().Select("folder_id AS key, COUNT(*) AS count").Group("folder_id").Scan(&folderRows).Error; err != nil {
return nil, nil, nil, err
}
return kindRows, categoryRows, folderRows, nil
}
func userAssetFilteredQuery(query *gorm.DB, filter UserAssetPageFilter, includeSearch bool) *gorm.DB {
if value := strings.TrimSpace(filter.Kind); value != "" {
query = query.Where("kind = ?", value)
}
if value := strings.TrimSpace(filter.Category); value != "" {
query = query.Where("category = ?", value)
}
if filter.Uncategorized {
query = query.Where("folder_id = ''")
} else if filter.FolderID != nil {
query = query.Where("folder_id = ?", strings.TrimSpace(*filter.FolderID))
}
switch strings.TrimSpace(filter.Status) {
case "active":
query = query.Where("status <> ?", model.AssetVersionStatusArchived)
case "archived":
query = query.Where("status = ?", model.AssetVersionStatusArchived)
case "":
default:
query = query.Where("status = ?", strings.TrimSpace(filter.Status))
}
if includeSearch {
if value := strings.ToLower(strings.TrimSpace(filter.Query)); value != "" {
pattern := "%" + value + "%"
query = query.Where("LOWER(title) LIKE ? OR LOWER(payload_json) LIKE ?", pattern, pattern)
}
}
return query
}
func (r *Repository) AssetFolders(userID string) ([]model.AssetFolder, error) {
var folders []model.AssetFolder
err := r.db.Where("user_id = ?", userID).Order("position asc, created_at asc").Find(&folders).Error
return folders, err
}
func (r *Repository) AssetFolderForUser(userID string, folderID string) (*model.AssetFolder, error) {
var folder model.AssetFolder
if err := r.db.First(&folder, "id = ? AND user_id = ?", folderID, userID).Error; err != nil {
return nil, err
}
return &folder, nil
}
func (r *Repository) AssetFolderNameExists(userID string, nameKey string, excludeID string) (bool, error) {
query := r.db.Model(&model.AssetFolder{}).Where("user_id = ? AND name_key = ?", userID, nameKey)
if excludeID != "" {
query = query.Where("id <> ?", excludeID)
}
var count int64
err := query.Count(&count).Error
return count > 0, err
}
func (r *Repository) NextAssetFolderPosition(userID string) (int, error) {
var row struct{ Maximum int }
err := r.db.Model(&model.AssetFolder{}).Select("COALESCE(MAX(position), -1) AS maximum").Where("user_id = ?", userID).Scan(&row).Error
return row.Maximum + 1, err
}
func (r *Repository) CreateAssetFolder(folder *model.AssetFolder) error {
return r.db.Create(folder).Error
}
func (r *Repository) UpdateAssetFolder(folder *model.AssetFolder) error {
result := r.db.Model(&model.AssetFolder{}).Where("id = ? AND user_id = ?", folder.ID, folder.UserID).Updates(map[string]any{
"name": folder.Name, "name_key": folder.NameKey, "updated_at": folder.UpdatedAt,
})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
}
func (r *Repository) MoveUserAssetsToFolder(userID string, assetIDs []string, folderID string) error {
return r.db.Transaction(func(tx *gorm.DB) error {
return moveUserAssetsToFolder(tx, userID, assetIDs, folderID)
})
}
func (r *Repository) DeleteAssetFolder(userID string, folderID string) error {
return r.db.Transaction(func(tx *gorm.DB) error {
var folder model.AssetFolder
if err := tx.First(&folder, "id = ? AND user_id = ?", folderID, userID).Error; err != nil {
return err
}
var assets []model.Asset
if err := tx.Where("user_id = ? AND folder_id = ?", userID, folderID).Find(&assets).Error; err != nil {
return err
}
ids := make([]string, len(assets))
for index := range assets {
ids[index] = assets[index].ID
}
if err := moveUserAssetsToFolder(tx, userID, ids, ""); err != nil {
return err
}
result := tx.Delete(&model.AssetFolder{}, "id = ? AND user_id = ?", folderID, userID)
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrRecordNotFound
}
return nil
})
}
func moveUserAssetsToFolder(tx *gorm.DB, userID string, assetIDs []string, folderID string) error {
if len(assetIDs) == 0 {
return nil
}
var assets []model.Asset
if err := tx.Where("user_id = ? AND id IN ?", userID, assetIDs).Find(&assets).Error; err != nil {
return err
}
if len(assets) != len(assetIDs) {
return gorm.ErrRecordNotFound
}
now := time.Now().UTC()
for index := range assets {
payloadJSON, err := assetPayloadWithFolder(assets[index].PayloadJSON, folderID, now)
if err != nil {
return err
}
if err := tx.Model(&model.Asset{}).Where("id = ? AND user_id = ?", assets[index].ID, userID).Updates(map[string]any{
"folder_id": folderID, "payload_json": payloadJSON, "updated_at": now,
}).Error; err != nil {
return err
}
}
return nil
}
func assetPayloadWithFolder(payloadJSON string, folderID string, updatedAt time.Time) (string, error) {
var payload map[string]any
if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
return "", err
}
if folderID == "" {
delete(payload, "folderId")
} else {
payload["folderId"] = folderID
}
payload["updatedAt"] = updatedAt.Format(time.RFC3339Nano)
encoded, err := json.Marshal(payload)
return string(encoded), err
}
func IsAssetFolderNotFound(err error) bool {
return errors.Is(err, gorm.ErrRecordNotFound)
}