Files
ttstd-infinite-canvas/backend/internal/repository/repository_assets_canvases.go
T

161 lines
5.7 KiB
Go

// 素材与画布项目的基础读写。
//
// 素材的彻底删除(含引用、Outbox)在 resource_reference.go 的 DeleteAssetsAndResources。
package repository
import (
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"yingce/backend/internal/model"
)
func (r *Repository) Assets(userID string) ([]model.Asset, error) {
var assets []model.Asset
err := r.db.Order("updated_at desc").Find(&assets, "user_id = ?", userID).Error
return assets, err
}
func (r *Repository) AssetSummaries(userID string) ([]model.Asset, error) {
var assets []model.Asset
err := r.db.Select("id", "folder_id", "kind", "category", "status", "primary_version_id", "title", "created_at", "updated_at").Order("updated_at desc").Find(&assets, "user_id = ?", userID).Error
return assets, err
}
func (r *Repository) AssetForUser(userID string, id string) (*model.Asset, error) {
var asset model.Asset
if err := r.db.First(&asset, "id = ? AND user_id = ?", id, userID).Error; err != nil {
return nil, err
}
return &asset, nil
}
func (r *Repository) AssetsForUserIDs(userID string, ids []string) ([]model.Asset, error) {
if len(ids) == 0 {
return nil, nil
}
var assets []model.Asset
err := r.db.Find(&assets, "user_id = ? AND id IN ?", userID, ids).Error
return assets, err
}
func (r *Repository) UpsertAsset(asset *model.Asset) error {
result := r.db.Model(&model.Asset{}).
Where("id = ? AND user_id = ?", asset.ID, asset.UserID).
Updates(map[string]any{"folder_id": asset.FolderID, "kind": asset.Kind, "category": asset.Category, "status": asset.Status, "primary_version_id": asset.PrimaryVersionID, "title": asset.Title, "payload_json": asset.PayloadJSON, "updated_at": asset.UpdatedAt})
if result.Error != nil || result.RowsAffected > 0 {
return result.Error
}
return r.db.Create(asset).Error
}
func (r *Repository) DeleteAsset(userID string, id string) error {
return r.DeleteAssetAndResources(userID, id, nil, nil, false)
}
func (r *Repository) FindExpiredArchivedAssets(cutoff time.Time, limit int) ([]model.Asset, error) {
var assets []model.Asset
if limit <= 0 {
limit = 100
}
err := r.db.Where("status = ? AND updated_at <= ?", model.AssetVersionStatusArchived, cutoff).
Order("updated_at asc, id asc").
Limit(limit).
Find(&assets).Error
return assets, err
}
func (r *Repository) ReplaceAssets(userID string, assets []model.Asset) error {
return r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Delete(&model.Asset{}, "user_id = ?", userID).Error; err != nil {
return err
}
if len(assets) == 0 {
return nil
}
return tx.Create(&assets).Error
})
}
func (r *Repository) CanvasProjects(userID string) ([]model.CanvasProject, error) {
var projects []model.CanvasProject
err := r.db.Order("updated_at desc").Find(&projects, "user_id = ?", userID).Error
return projects, err
}
func (r *Repository) CanvasProjectSummaries(userID string) ([]model.CanvasProject, error) {
var projects []model.CanvasProject
err := r.db.Select("id", "title", "revision", "created_at", "updated_at").Order("updated_at desc").Find(&projects, "user_id = ?", userID).Error
return projects, err
}
func (r *Repository) CanvasProjectForUser(userID string, id string) (*model.CanvasProject, error) {
var project model.CanvasProject
if err := r.db.First(&project, "id = ? AND user_id = ?", id, userID).Error; err != nil {
return nil, err
}
return &project, nil
}
func (r *Repository) UpsertCanvasProject(project *model.CanvasProject) error {
expected := project.Revision
if expected < 0 {
return ErrCanvasRevisionConflict
}
if expected == 0 {
created := *project
created.Revision = 1
result := r.db.Clauses(clause.OnConflict{DoNothing: true}).Create(&created)
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return ErrCanvasRevisionConflict
}
project.Revision = 1
return nil
}
// The revision predicate and increment must be in the same SQL statement.
// A missing row is a conflict, never an invitation to recreate a deleted canvas.
result := r.db.Model(&model.CanvasProject{}).
Where("id = ? AND user_id = ? AND revision = ?", project.ID, project.UserID, expected).
Updates(map[string]any{"project_id": project.ProjectID, "title": project.Title, "payload_json": project.PayloadJSON, "updated_at": project.UpdatedAt, "revision": expected + 1})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return ErrCanvasRevisionConflict
}
project.Revision = expected + 1
return nil
}
func (r *Repository) DeleteCanvasProject(userID string, id string) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Serialize deletion with saves before reading the history IDs to remove.
if err := tx.Model(&model.CanvasProject{}).Where("user_id = ? AND id = ?", userID, id).UpdateColumn("revision", gorm.Expr("revision")).Error; err != nil {
return err
}
var snapshotIDs []string
if err := tx.Model(&model.CanvasSnapshot{}).Where("user_id = ? AND canvas_id = ?", userID, id).Pluck("id", &snapshotIDs).Error; err != nil {
return err
}
if err := deleteCanvasSnapshots(tx, snapshotIDs); err != nil {
return err
}
if err := tx.Where("user_id = ? AND project_id = ?", userID, id).Delete(&model.CanvasShare{}).Error; err != nil {
return err
}
if err := tx.Where("canvas_id = ?", id).Delete(&model.CanvasUnitLink{}).Error; err != nil {
return err
}
// 任务和会话是审计记录,不随独立画布实体保留归属 ID,避免删除后继续挂住画布上下文。
if err := tx.Model(&model.Task{}).Where("user_id = ? AND project_id = ?", userID, id).Update("project_id", "").Error; err != nil {
return err
}
return tx.Delete(&model.CanvasProject{}, "id = ? AND user_id = ?", id, userID).Error
})
}