161 lines
5.7 KiB
Go
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
|
|
})
|
|
}
|