576 lines
25 KiB
Go
576 lines
25 KiB
Go
// 短剧项目、分集单元、项目素材库与画布-单元关联。
|
||
|
||
package repository
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
"gorm.io/gorm/clause"
|
||
"infinite-canvas/backend/internal/model"
|
||
)
|
||
|
||
func (r *Repository) Projects(userID string) ([]model.Project, error) {
|
||
var projects []model.Project
|
||
err := r.db.Where("user_id = ?", userID).Order("updated_at desc").Find(&projects).Error
|
||
return projects, err
|
||
}
|
||
|
||
func (r *Repository) ProjectsPage(userID string, page int, pageSize int) ([]model.Project, int64, error) {
|
||
var projects []model.Project
|
||
var total int64
|
||
query := r.db.Model(&model.Project{}).Where("user_id = ?", userID)
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
if err := query.Order("updated_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&projects).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
return projects, total, nil
|
||
}
|
||
|
||
func (r *Repository) ProjectForUser(userID string, id string) (*model.Project, error) {
|
||
var project model.Project
|
||
if err := r.db.First(&project, "id = ? AND user_id = ?", id, userID).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &project, nil
|
||
}
|
||
|
||
func (r *Repository) CreateProject(project *model.Project) error {
|
||
return r.db.Create(project).Error
|
||
}
|
||
|
||
func (r *Repository) UpdateProject(project *model.Project) error {
|
||
return r.db.Model(&model.Project{}).Where("id = ? AND user_id = ?", project.ID, project.UserID).Updates(map[string]any{
|
||
"name": project.Name, "type": project.Type, "aspect_ratio": project.AspectRatio, "source_type": project.SourceType,
|
||
"description": project.Description, "cover_resource_id": project.CoverResourceID,
|
||
"style_preset_id": project.StylePresetID, "style_profile_json": project.StyleProfileJSON,
|
||
"default_image_model": project.DefaultImageModel, "default_video_model": project.DefaultVideoModel,
|
||
"status": project.Status, "revision": project.Revision, "updated_at": project.UpdatedAt,
|
||
}).Error
|
||
}
|
||
|
||
func (r *Repository) DeleteProject(userID string, id string, canvasUpdates []model.CanvasProject) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
var canvasIDs []string
|
||
if err := tx.Model(&model.CanvasProject{}).
|
||
Where("user_id = ? AND project_id = ?", userID, id).
|
||
Pluck("id", &canvasIDs).Error; err != nil {
|
||
return err
|
||
}
|
||
projectScopeIDs := append([]string{id}, canvasIDs...)
|
||
var activeTaskCount int64
|
||
if err := tx.Model(&model.Task{}).
|
||
Where("user_id = ? AND project_id IN ? AND status IN ?", userID, projectScopeIDs, []model.TaskStatus{model.TaskStatusQueued, model.TaskStatusRunning}).
|
||
Count(&activeTaskCount).Error; err != nil {
|
||
return err
|
||
}
|
||
if activeTaskCount > 0 {
|
||
return ErrProjectHasActiveTasks
|
||
}
|
||
if err := tx.Where("user_id = ? AND project_id = ?", userID, id).Delete(&model.CanvasShare{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.CanvasUnitLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
for _, canvas := range canvasUpdates {
|
||
result := tx.Model(&model.CanvasProject{}).
|
||
Where("id = ? AND user_id = ? AND project_id = ? AND revision = ?", canvas.ID, userID, id, canvas.Revision).
|
||
Updates(map[string]any{"project_id": "", "payload_json": canvas.PayloadJSON, "updated_at": canvas.UpdatedAt, "revision": canvas.Revision + 1})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
}
|
||
var remainingCanvasCount int64
|
||
if err := tx.Model(&model.CanvasProject{}).Where("user_id = ? AND project_id = ?", userID, id).Count(&remainingCanvasCount).Error; err != nil {
|
||
return err
|
||
}
|
||
if remainingCanvasCount != 0 {
|
||
return fmt.Errorf("项目删除时仍有 %d 个画布关联未处理", remainingCanvasCount)
|
||
}
|
||
shotIDs := tx.Model(&model.Shot{}).Select("id").Where("project_id = ?", id)
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ProductionTaskLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ShotArtifact{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("shot_id IN (?)", shotIDs).Delete(&model.ShotRevision{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("shot_id IN (?)", shotIDs).Delete(&model.ShotAssetReference{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.Shot{}).Error; err != nil {
|
||
return err
|
||
}
|
||
instanceIDs := tx.Model(&model.WorkflowInstance{}).Select("id").Where("project_id = ?", id)
|
||
stepIDs := tx.Model(&model.WorkflowStepInstance{}).Select("id").Where("workflow_instance_id IN (?)", instanceIDs)
|
||
if err := tx.Where("workflow_step_id IN (?)", stepIDs).Delete(&model.WorkflowStepTask{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("workflow_instance_id IN (?)", instanceIDs).Delete(&model.WorkflowStepInstance{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.WorkflowInstance{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ProjectAssetLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ProjectAssetFolder{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ProjectAssetCandidate{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ?", id).Delete(&model.ProjectUnit{}).Error; err != nil {
|
||
return err
|
||
}
|
||
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.Project{}, "id = ? AND user_id = ?", id, userID).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) BumpProjectRevision(projectID string) error {
|
||
return r.db.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
}
|
||
|
||
func (r *Repository) ProjectUnits(projectID string) ([]model.ProjectUnit, error) {
|
||
var units []model.ProjectUnit
|
||
err := r.db.Where("project_id = ?", projectID).Order("position asc, created_at asc").Find(&units).Error
|
||
return units, err
|
||
}
|
||
|
||
func (r *Repository) ProjectUnitSummaries(projectID string) ([]model.ProjectUnit, error) {
|
||
var units []model.ProjectUnit
|
||
err := r.db.Select("id", "project_id", "kind", "title", "word_count", "status", "position", "created_at", "updated_at").Where("project_id = ?", projectID).Order("position asc, created_at asc").Find(&units).Error
|
||
return units, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetCount(projectID string) (int64, error) {
|
||
var count int64
|
||
err := r.db.Model(&model.ProjectAssetLink{}).Where("project_id = ?", projectID).Count(&count).Error
|
||
return count, err
|
||
}
|
||
|
||
func (r *Repository) CreateProjectUnit(unit *model.ProjectUnit) error {
|
||
return r.db.Create(unit).Error
|
||
}
|
||
|
||
func (r *Repository) ImportProjectUnits(projectID string, units []model.ProjectUnit) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
// 分批写入仍处于同一事务,避免两千章导入超过 SQLite/PostgreSQL 单语句参数上限。
|
||
if err := tx.CreateInBatches(&units, 100).Error; err != nil {
|
||
return err
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) ReorderProjectUnits(projectID string, unitIDs []string) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
now := time.Now()
|
||
for position, unitID := range unitIDs {
|
||
result := tx.Model(&model.ProjectUnit{}).Where("id = ? AND project_id = ?", unitID, projectID).Updates(map[string]any{"position": position, "updated_at": now})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": now}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) ProjectUnit(projectID string, id string) (*model.ProjectUnit, error) {
|
||
var unit model.ProjectUnit
|
||
if err := r.db.First(&unit, "id = ? AND project_id = ?", id, projectID).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &unit, nil
|
||
}
|
||
|
||
func (r *Repository) UpdateProjectUnit(unit *model.ProjectUnit, invalidateWorkflow bool) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
result := tx.Model(&model.ProjectUnit{}).Where("id = ? AND project_id = ?", unit.ID, unit.ProjectID).Updates(map[string]any{
|
||
"parent_id": unit.ParentID, "title": unit.Title, "source_text": unit.SourceText, "word_count": unit.WordCount, "status": unit.Status, "position": unit.Position, "updated_at": unit.UpdatedAt,
|
||
})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
if invalidateWorkflow {
|
||
if err := tx.Model(&model.ShotArtifact{}).Where("project_id = ? AND unit_id = ? AND status NOT IN ?", unit.ProjectID, unit.ID, []string{"failed", "stale"}).Updates(map[string]any{"status": "stale", "selected": false, "updated_at": unit.UpdatedAt}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := invalidateUnitWorkflowTx(tx, unit.ProjectID, unit.ID, "story", unit.UpdatedAt); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", unit.ProjectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": unit.UpdatedAt}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) DeleteProjectUnit(projectID string, id string) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.CanvasUnitLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
shotIDs := tx.Model(&model.Shot{}).Select("id").Where("project_id = ? AND unit_id = ?", projectID, id)
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.ProductionTaskLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.ShotArtifact{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("shot_id IN (?)", shotIDs).Delete(&model.ShotRevision{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("shot_id IN (?)", shotIDs).Delete(&model.ShotAssetReference{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.Shot{}).Error; err != nil {
|
||
return err
|
||
}
|
||
instanceIDs := tx.Model(&model.WorkflowInstance{}).Select("id").Where("project_id = ? AND unit_id = ?", projectID, id)
|
||
stepIDs := tx.Model(&model.WorkflowStepInstance{}).Select("id").Where("workflow_instance_id IN (?)", instanceIDs)
|
||
if err := tx.Where("workflow_step_id IN (?)", stepIDs).Delete(&model.WorkflowStepTask{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("workflow_instance_id IN (?)", instanceIDs).Delete(&model.WorkflowStepInstance{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.WorkflowInstance{}).Error; err != nil {
|
||
return err
|
||
}
|
||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, id).Delete(&model.ProjectAssetCandidate{}).Error; err != nil {
|
||
return err
|
||
}
|
||
result := tx.Delete(&model.ProjectUnit{}, "id = ? AND project_id = ?", id, projectID)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) CanvasUnitLink(projectID string, canvasID string, unitID string) (*model.CanvasUnitLink, error) {
|
||
var link model.CanvasUnitLink
|
||
if err := r.db.First(&link, "project_id = ? AND canvas_id = ? AND unit_id = ?", projectID, canvasID, unitID).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &link, nil
|
||
}
|
||
|
||
func (r *Repository) UpsertCanvasUnitLink(link *model.CanvasUnitLink) error {
|
||
result := r.db.Model(&model.CanvasUnitLink{}).Where("project_id = ? AND canvas_id = ? AND unit_id = ?", link.ProjectID, link.CanvasID, link.UnitID).Updates(map[string]any{"role": link.Role})
|
||
if result.Error != nil || result.RowsAffected > 0 {
|
||
return result.Error
|
||
}
|
||
return r.db.Create(link).Error
|
||
}
|
||
|
||
func (r *Repository) ProjectCanvasSummaries(userID string, projectID string) ([]model.CanvasProject, error) {
|
||
var canvases []model.CanvasProject
|
||
err := r.db.Select("id", "user_id", "project_id", "title", "revision", "created_at", "updated_at").Where("user_id = ? AND project_id = ?", userID, projectID).Order("updated_at desc").Find(&canvases).Error
|
||
return canvases, err
|
||
}
|
||
|
||
func (r *Repository) ProjectCanvasDocuments(userID string, projectID string) ([]model.CanvasProject, error) {
|
||
var canvases []model.CanvasProject
|
||
err := r.db.Select("id", "title", "payload_json", "revision").Where("user_id = ? AND project_id = ?", userID, projectID).Find(&canvases).Error
|
||
return canvases, err
|
||
}
|
||
|
||
func (r *Repository) ProjectCanvasUnitLinks(projectID string) ([]model.CanvasUnitLink, error) {
|
||
var links []model.CanvasUnitLink
|
||
err := r.db.Where("project_id = ?", projectID).Order("created_at asc").Find(&links).Error
|
||
return links, err
|
||
}
|
||
|
||
func (r *Repository) DeleteCanvasUnitLink(projectID string, canvasID string, unitID string) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
result := tx.Delete(&model.CanvasUnitLink{}, "project_id = ? AND canvas_id = ? AND unit_id = ?", projectID, canvasID, unitID)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) AssignCanvasToProject(userID string, canvasID string, projectID string) error {
|
||
return r.db.Model(&model.CanvasProject{}).Where("id = ? AND user_id = ?", canvasID, userID).Updates(map[string]any{"project_id": projectID, "revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
}
|
||
|
||
func (r *Repository) UnassignCanvasFromProject(userID string, projectID string, canvasID string, payloadJSON string, updatedAt time.Time, revision int64) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
if err := tx.Where("project_id = ? AND canvas_id = ?", projectID, canvasID).Delete(&model.CanvasUnitLink{}).Error; err != nil {
|
||
return err
|
||
}
|
||
result := tx.Model(&model.CanvasProject{}).Where("id = ? AND user_id = ? AND project_id = ? AND revision = ?", canvasID, userID, projectID, revision).Updates(map[string]any{
|
||
"project_id": "", "payload_json": payloadJSON, "updated_at": updatedAt, "revision": revision + 1,
|
||
})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": updatedAt}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) ProjectAssets(userID string, projectID string) ([]model.Asset, error) {
|
||
var assets []model.Asset
|
||
err := r.db.Table("assets").Select("assets.*").Joins("JOIN project_asset_links ON project_asset_links.asset_id = assets.id").Where("assets.user_id = ? AND project_asset_links.project_id = ?", userID, projectID).Order("assets.updated_at desc").Scan(&assets).Error
|
||
return assets, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetLinks(projectID string) ([]model.ProjectAssetLink, error) {
|
||
var links []model.ProjectAssetLink
|
||
err := r.db.Where("project_id = ?", projectID).Order("folder_id asc, position asc, created_at asc").Find(&links).Error
|
||
return links, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetLink(projectID string, assetID string) (*model.ProjectAssetLink, error) {
|
||
var link model.ProjectAssetLink
|
||
if err := r.db.First(&link, "project_id = ? AND asset_id = ?", projectID, assetID).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &link, nil
|
||
}
|
||
|
||
func (r *Repository) NextProjectAssetPosition(projectID string, folderID string) (int, error) {
|
||
var result struct{ Maximum int }
|
||
err := r.db.Model(&model.ProjectAssetLink{}).
|
||
Select("COALESCE(MAX(position), -1) AS maximum").
|
||
Where("project_id = ? AND folder_id = ?", projectID, folderID).
|
||
Scan(&result).Error
|
||
return result.Maximum + 1, err
|
||
}
|
||
|
||
func (r *Repository) MoveProjectAsset(projectID string, assetID string, folderID string, position int) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
result := tx.Model(&model.ProjectAssetLink{}).
|
||
Where("project_id = ? AND asset_id = ?", projectID, assetID).
|
||
Updates(map[string]any{"folder_id": folderID, "position": position})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).
|
||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetFolders(projectID string) ([]model.ProjectAssetFolder, error) {
|
||
var folders []model.ProjectAssetFolder
|
||
err := r.db.Where("project_id = ?", projectID).Order("parent_id asc, position asc, created_at asc").Find(&folders).Error
|
||
return folders, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetFolder(projectID string, folderID string) (*model.ProjectAssetFolder, error) {
|
||
var folder model.ProjectAssetFolder
|
||
if err := r.db.First(&folder, "id = ? AND project_id = ?", folderID, projectID).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &folder, nil
|
||
}
|
||
|
||
func (r *Repository) CreateProjectAssetFolder(folder *model.ProjectAssetFolder) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
if err := tx.Create(folder).Error; err != nil {
|
||
return err
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", folder.ProjectID).
|
||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": folder.UpdatedAt}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) UpdateProjectAssetFolder(folder *model.ProjectAssetFolder) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
result := tx.Model(&model.ProjectAssetFolder{}).
|
||
Where("id = ? AND project_id = ?", folder.ID, folder.ProjectID).
|
||
Updates(map[string]any{"parent_id": folder.ParentID, "name": folder.Name, "name_key": folder.NameKey, "style": folder.Style, "theme": folder.Theme, "position": folder.Position, "updated_at": folder.UpdatedAt})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", folder.ProjectID).
|
||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": folder.UpdatedAt}).Error
|
||
})
|
||
}
|
||
|
||
func (r *Repository) DeleteProjectAssetFolder(projectID string, folderID string) error {
|
||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||
var childCount int64
|
||
if err := tx.Model(&model.ProjectAssetFolder{}).Where("project_id = ? AND parent_id = ?", projectID, folderID).Count(&childCount).Error; err != nil {
|
||
return err
|
||
}
|
||
var assetCount int64
|
||
if err := tx.Model(&model.ProjectAssetLink{}).Where("project_id = ? AND folder_id = ?", projectID, folderID).Count(&assetCount).Error; err != nil {
|
||
return err
|
||
}
|
||
if childCount > 0 || assetCount > 0 {
|
||
return ErrProjectAssetFolderNotEmpty
|
||
}
|
||
result := tx.Delete(&model.ProjectAssetFolder{}, "id = ? AND project_id = ?", folderID, projectID)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", projectID).
|
||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
}
|
||
|
||
// LinkProjectAsset 将首版本、素材领域字段、项目引用和修订号原子提交,避免产生半关联资产。
|
||
// 资产首次入库也在此事务内完成(service 层只做内存构造,不预落库),
|
||
// 事务失败时资产一并回滚,不再留下“有资产无链接”的孤儿资产。
|
||
func (r *Repository) LinkProjectAsset(asset *model.Asset, version *model.AssetVersion, link *model.ProjectAssetLink) (bool, error) {
|
||
createdLink := false
|
||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||
// 资产可能尚未落库(首次导入)或已存在(并发/重试),冲突幂等跳过。
|
||
assetCreated := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "id"}}, DoNothing: true}).Create(asset)
|
||
if assetCreated.Error != nil {
|
||
return assetCreated.Error
|
||
}
|
||
created := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "project_id"}, {Name: "asset_id"}}, DoNothing: true}).Create(link)
|
||
if created.Error != nil {
|
||
return created.Error
|
||
}
|
||
if created.RowsAffected == 0 {
|
||
return nil
|
||
}
|
||
createdLink = true
|
||
if version != nil {
|
||
if err := tx.Create(version).Error; err != nil {
|
||
return err
|
||
}
|
||
}
|
||
result := tx.Model(&model.Asset{}).Where("id = ? AND user_id = ?", asset.ID, asset.UserID).Updates(map[string]any{
|
||
"category": asset.Category, "status": asset.Status, "primary_version_id": asset.PrimaryVersionID, "updated_at": asset.UpdatedAt,
|
||
})
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected != 1 {
|
||
return gorm.ErrRecordNotFound
|
||
}
|
||
return tx.Model(&model.Project{}).Where("id = ?", link.ProjectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": time.Now()}).Error
|
||
})
|
||
return createdLink, err
|
||
}
|
||
|
||
func (r *Repository) DeleteProjectAssetLink(projectID string, assetID string) error {
|
||
return r.db.Delete(&model.ProjectAssetLink{}, "project_id = ? AND asset_id = ?", projectID, assetID).Error
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetShotReferenceCount(projectID string, assetID string) (int64, error) {
|
||
var count int64
|
||
err := r.db.Table("shot_asset_references").
|
||
Joins("JOIN shots ON shots.id = shot_asset_references.shot_id").
|
||
Joins("JOIN asset_versions ON asset_versions.id = shot_asset_references.asset_version_id").
|
||
Where("shots.project_id = ? AND asset_versions.asset_id = ?", projectID, assetID).
|
||
Count(&count).Error
|
||
return count, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetLinked(projectID string, assetID string) (bool, error) {
|
||
var count int64
|
||
err := r.db.Model(&model.ProjectAssetLink{}).Where("project_id = ? AND asset_id = ?", projectID, assetID).Count(&count).Error
|
||
return count > 0, err
|
||
}
|
||
|
||
func (r *Repository) AssetReferenceCount(assetID string) (int64, error) {
|
||
var projectLinks int64
|
||
if err := r.db.Model(&model.ProjectAssetLink{}).Where("asset_id = ?", assetID).Count(&projectLinks).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
var shotLinks int64
|
||
err := r.db.Table("shot_asset_references").Joins("JOIN asset_versions ON asset_versions.id = shot_asset_references.asset_version_id").Where("asset_versions.asset_id = ?", assetID).Count(&shotLinks).Error
|
||
return projectLinks + shotLinks, err
|
||
}
|
||
|
||
func (r *Repository) UpdateAssetDomain(asset *model.Asset) error {
|
||
return r.db.Model(&model.Asset{}).Where("id = ? AND user_id = ?", asset.ID, asset.UserID).Updates(map[string]any{"category": asset.Category, "status": asset.Status, "primary_version_id": asset.PrimaryVersionID, "updated_at": asset.UpdatedAt}).Error
|
||
}
|
||
|
||
func (r *Repository) AssetVersions(assetID string) ([]model.AssetVersion, error) {
|
||
var versions []model.AssetVersion
|
||
err := r.db.Where("asset_id = ?", assetID).Order("version desc").Find(&versions).Error
|
||
return versions, err
|
||
}
|
||
|
||
func (r *Repository) ProjectAssetUsageRoles(projectID string, assetID string) ([]string, error) {
|
||
var shotRoles []string
|
||
if err := r.db.Table("shot_asset_references").
|
||
Distinct("shot_asset_references.role").
|
||
Joins("JOIN shots ON shots.id = shot_asset_references.shot_id").
|
||
Joins("JOIN asset_versions ON asset_versions.id = shot_asset_references.asset_version_id").
|
||
Where("shots.project_id = ? AND asset_versions.asset_id = ?", projectID, assetID).
|
||
Order("shot_asset_references.role asc").
|
||
Pluck("shot_asset_references.role", &shotRoles).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
var representationRoles []string
|
||
if err := r.db.Table("asset_representations").
|
||
Distinct("asset_representations.role").
|
||
Joins("JOIN asset_versions ON asset_versions.id = asset_representations.asset_version_id").
|
||
Joins("JOIN project_asset_links ON project_asset_links.asset_id = asset_versions.asset_id").
|
||
Where("project_asset_links.project_id = ? AND asset_versions.asset_id = ?", projectID, assetID).
|
||
Pluck("asset_representations.role", &representationRoles).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
seen := make(map[string]struct{}, len(shotRoles)+len(representationRoles))
|
||
for _, role := range append(shotRoles, representationRoles...) {
|
||
if role != "" {
|
||
seen[role] = struct{}{}
|
||
}
|
||
}
|
||
roles := make([]string, 0, len(seen))
|
||
for role := range seen {
|
||
roles = append(roles, role)
|
||
}
|
||
sort.Strings(roles)
|
||
return roles, nil
|
||
}
|
||
|
||
func (r *Repository) AssetVersionForProject(projectID string, versionID string) (*model.AssetVersion, error) {
|
||
var version model.AssetVersion
|
||
err := r.db.Table("asset_versions").Select("asset_versions.*").Joins("JOIN project_asset_links ON project_asset_links.asset_id = asset_versions.asset_id").Where("project_asset_links.project_id = ? AND asset_versions.id = ?", projectID, versionID).First(&version).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &version, nil
|
||
}
|
||
|
||
func (r *Repository) CreateAssetVersion(version *model.AssetVersion) error {
|
||
return r.db.Create(version).Error
|
||
}
|