feat: 完成短剧项目空间与结构化资产创作闭环 (#38)
* feat(canvas): 短剧项目空间 - 建立项目章节与画布关联 MVP * feat(canvas): 短剧项目空间 - 完善项目资产、镜头与流程闭环 Refs #30 * fix(deploy): 本地部署 - 修复前端构建并忽略本地 PostgreSQL 配置 * feat(*): 短剧项目空间 - 完成项目、章节、资产、角色与画布创作闭环 Refs #30 --------- Co-authored-by: ddCat <ddcat666@88.com>
This commit is contained in:
1 parent
431b9e3294
commit
d2fbc413b3
208 files changed
+22655
-4234
No files matched your search
@@ -2,6 +2,7 @@ package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -387,7 +388,7 @@ func (r *Repository) Tasks(userID string, limit int, projectID string, activeOnl
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
query := r.db.Select("id", "session_id", "project_id", "type", "status", "stage", "progress", "prompt", "operation", "provider", "model", "billing_order_id", "attempts", "started_at", "completed_at", "created_at", "updated_at").
|
||||
query := r.db.Select("id", "session_id", "project_id", "type", "status", "stage", "progress", "prompt", "operation", "provider", "model", "result_json", "billing_order_id", "attempts", "started_at", "completed_at", "created_at", "updated_at").
|
||||
Where("user_id = ?", userID)
|
||||
if strings.TrimSpace(projectID) != "" {
|
||||
query = query.Where("project_id = ?", strings.TrimSpace(projectID))
|
||||
@@ -460,6 +461,12 @@ func (r *Repository) SystemChannels(includeDisabled bool) ([]model.ModelChannel,
|
||||
return channels, err
|
||||
}
|
||||
|
||||
func (r *Repository) HistoricalSystemChannelReferences() ([]model.ModelChannel, error) {
|
||||
var channels []model.ModelChannel
|
||||
err := r.db.Unscoped().Select("id", "name").Where("scope = ?", model.ChannelScopeSystem).Order("created_at asc").Find(&channels).Error
|
||||
return channels, err
|
||||
}
|
||||
|
||||
func (r *Repository) AdminSystemChannels(keyword string, interfaceType string, status string, limit int, offset int) ([]model.ModelChannel, int64, error) {
|
||||
var channels []model.ModelChannel
|
||||
var total int64
|
||||
@@ -507,6 +514,28 @@ func (r *Repository) AdminSystemChannel(id string) (*model.ModelChannel, error)
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteSystemChannel(id string) error {
|
||||
now := time.Now()
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
channelResult := tx.Model(&model.ModelChannel{}).
|
||||
Where("id = ? AND scope = ?", id, model.ChannelScopeSystem).
|
||||
Updates(map[string]any{"api_key": "", "enabled": false, "updated_at": now})
|
||||
if channelResult.Error != nil {
|
||||
return channelResult.Error
|
||||
}
|
||||
if channelResult.RowsAffected != 1 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
if err := tx.Model(&model.ChannelModel{}).Where("channel_id = ?", id).Updates(map[string]any{"enabled": false, "updated_at": now}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("channel_id = ?", id).Delete(&model.ChannelModel{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where("id = ? AND scope = ?", id, model.ChannelScopeSystem).Delete(&model.ModelChannel{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) ApiCallLogs(userID string, admin bool, limit int) ([]model.ApiCallLog, error) {
|
||||
var logs []model.ApiCallLog
|
||||
if limit <= 0 || limit > 200 {
|
||||
@@ -634,7 +663,7 @@ func (r *Repository) Assets(userID string) ([]model.Asset, error) {
|
||||
|
||||
func (r *Repository) AssetSummaries(userID string) ([]model.Asset, error) {
|
||||
var assets []model.Asset
|
||||
err := r.db.Select("id", "kind", "title", "created_at", "updated_at").Order("updated_at desc").Find(&assets, "user_id = ?", userID).Error
|
||||
err := r.db.Select("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
|
||||
}
|
||||
|
||||
@@ -649,7 +678,7 @@ func (r *Repository) AssetForUser(userID string, id string) (*model.Asset, error
|
||||
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{"kind": asset.Kind, "title": asset.Title, "payload_json": asset.PayloadJSON, "updated_at": asset.UpdatedAt})
|
||||
Updates(map[string]any{"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
|
||||
}
|
||||
@@ -657,7 +686,19 @@ func (r *Repository) UpsertAsset(asset *model.Asset) error {
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteAsset(userID string, id string) error {
|
||||
return r.db.Delete(&model.Asset{}, "id = ? AND user_id = ?", id, userID).Error
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
versionIDs := tx.Model(&model.AssetVersion{}).Select("id").Where("asset_id = ?", id)
|
||||
if err := tx.Where("asset_version_id IN (?)", versionIDs).Delete(&model.CharacterVoiceBinding{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("asset_version_id IN (?)", versionIDs).Delete(&model.AssetRepresentation{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("asset_id = ?", id).Delete(&model.AssetVersion{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&model.Asset{}, "id = ? AND user_id = ?", id, userID).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) ReplaceAssets(userID string, assets []model.Asset) error {
|
||||
@@ -695,7 +736,7 @@ func (r *Repository) CanvasProjectForUser(userID string, id string) (*model.Canv
|
||||
func (r *Repository) UpsertCanvasProject(project *model.CanvasProject) error {
|
||||
result := r.db.Model(&model.CanvasProject{}).
|
||||
Where("id = ? AND user_id = ?", project.ID, project.UserID).
|
||||
Updates(map[string]any{"title": project.Title, "payload_json": project.PayloadJSON, "updated_at": project.UpdatedAt})
|
||||
Updates(map[string]any{"project_id": project.ProjectID, "title": project.Title, "payload_json": project.PayloadJSON, "updated_at": project.UpdatedAt})
|
||||
if result.Error != nil || result.RowsAffected > 0 {
|
||||
return result.Error
|
||||
}
|
||||
@@ -706,6 +747,623 @@ func (r *Repository) DeleteCanvasProject(userID string, id string) error {
|
||||
return r.db.Delete(&model.CanvasProject{}, "id = ? AND user_id = ?", id, userID).Error
|
||||
}
|
||||
|
||||
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) 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, "style_preset_id": project.StylePresetID, "active_task_limit": project.ActiveTaskLimit,
|
||||
"status": project.Status, "revision": project.Revision, "updated_at": project.UpdatedAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteProject(userID string, id string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.CanvasProject{}).Where("user_id = ? AND project_id = ?", userID, id).Update("project_id", "").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("project_id = ?", id).Delete(&model.CanvasUnitLink{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
shotIDs := tx.Model(&model.Shot{}).Select("id").Where("project_id = ?", id)
|
||||
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.ProjectAssetCandidate{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("project_id = ?", id).Delete(&model.ProjectUnit{}).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", "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) error {
|
||||
return r.db.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, "status": unit.Status, "position": unit.Position, "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("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", "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").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).Update("project_id", projectID).Error
|
||||
}
|
||||
|
||||
func (r *Repository) UnassignCanvasFromProject(userID string, projectID string, canvasID string, payloadJSON string, updatedAt time.Time) 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 = ?", canvasID, userID, projectID).Updates(map[string]any{
|
||||
"project_id": "", "payload_json": payloadJSON, "updated_at": updatedAt,
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
// LinkProjectAsset 将首版本、素材领域字段、项目引用和修订号原子提交,避免产生半关联资产。
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (r *Repository) ProjectShots(projectID string) ([]model.Shot, error) {
|
||||
var shots []model.Shot
|
||||
err := r.db.Where("project_id = ?", projectID).Order("unit_id asc, position asc").Find(&shots).Error
|
||||
return shots, err
|
||||
}
|
||||
|
||||
func (r *Repository) SaveShot(shot *model.Shot, create bool) error {
|
||||
if create {
|
||||
return r.db.Create(shot).Error
|
||||
}
|
||||
return r.db.Model(&model.Shot{}).Where("id = ? AND project_id = ?", shot.ID, shot.ProjectID).Updates(map[string]any{
|
||||
"unit_id": shot.UnitID, "title": shot.Title, "description": shot.Description, "position": shot.Position,
|
||||
"duration_ms": shot.DurationMs, "status": shot.Status, "updated_at": shot.UpdatedAt,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ReplaceProjectUnitShots(projectID string, unitID string, shots []model.Shot) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
shotIDs := tx.Model(&model.Shot{}).Select("id").Where("project_id = ? AND unit_id = ?", projectID, unitID)
|
||||
if err := tx.Where("shot_id IN (?)", shotIDs).Delete(&model.ShotAssetReference{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("project_id = ? AND shot_id IN (?)", projectID, shotIDs).Delete(&model.ProjectAssetCandidate{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("project_id = ? AND unit_id = ?", projectID, unitID).Delete(&model.Shot{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&shots).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) ShotForProject(projectID string, shotID string) (*model.Shot, error) {
|
||||
var shot model.Shot
|
||||
if err := r.db.First(&shot, "id = ? AND project_id = ?", shotID, projectID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &shot, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpsertShotAssetReference(reference *model.ShotAssetReference) error {
|
||||
result := r.db.Model(&model.ShotAssetReference{}).Where("shot_id = ? AND asset_version_id = ? AND role = ?", reference.ShotID, reference.AssetVersionID, reference.Role).Updates(map[string]any{"status": reference.Status})
|
||||
if result.Error != nil || result.RowsAffected > 0 {
|
||||
return result.Error
|
||||
}
|
||||
return r.db.Create(reference).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ProjectShotAssetReferences(projectID string) ([]model.ShotAssetReference, error) {
|
||||
var references []model.ShotAssetReference
|
||||
err := r.db.Table("shot_asset_references").Select("shot_asset_references.*").
|
||||
Joins("JOIN shots ON shots.id = shot_asset_references.shot_id").
|
||||
Where("shots.project_id = ?", projectID).
|
||||
Order("shot_asset_references.created_at asc").Scan(&references).Error
|
||||
return references, err
|
||||
}
|
||||
|
||||
func (r *Repository) ProjectAssetCandidates(projectID string) ([]model.ProjectAssetCandidate, error) {
|
||||
var candidates []model.ProjectAssetCandidate
|
||||
err := r.db.Where("project_id = ?", projectID).Order("created_at asc").Find(&candidates).Error
|
||||
return candidates, err
|
||||
}
|
||||
|
||||
func (r *Repository) ProjectAssetCandidate(projectID string, candidateID string) (*model.ProjectAssetCandidate, error) {
|
||||
var candidate model.ProjectAssetCandidate
|
||||
if err := r.db.First(&candidate, "id = ? AND project_id = ?", candidateID, projectID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &candidate, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateProjectAssetCandidates(candidates []model.ProjectAssetCandidate) error {
|
||||
if len(candidates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.db.Create(&candidates).Error
|
||||
}
|
||||
|
||||
// ConfirmProjectAssetCandidate 将正式资产身份、首版本、项目引用和候选状态放在同一事务中,避免出现半确认数据。
|
||||
func (r *Repository) ConfirmProjectAssetCandidate(candidate *model.ProjectAssetCandidate, asset *model.Asset, version *model.AssetVersion, link *model.ProjectAssetLink, createAsset bool) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if createAsset {
|
||||
if err := tx.Create(asset).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(version).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tx.First(&model.Asset{}, "id = ? AND user_id = ?", asset.ID, asset.UserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("project_id = ? AND asset_id = ?", link.ProjectID, link.AssetID).FirstOrCreate(link).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result := tx.Model(&model.ProjectAssetCandidate{}).
|
||||
Where("id = ? AND project_id = ? AND status = ?", candidate.ID, candidate.ProjectID, "pending_confirmation").
|
||||
Updates(map[string]any{"status": candidate.Status, "resolved_asset_id": candidate.ResolvedAssetID, "updated_at": candidate.UpdatedAt})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
return tx.Model(&model.Project{}).Where("id = ?", candidate.ProjectID).
|
||||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": candidate.UpdatedAt}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) WorkflowTemplateVersion(templateKey string, version int) (*model.WorkflowTemplateVersion, error) {
|
||||
var template model.WorkflowTemplateVersion
|
||||
if err := r.db.First(&template, "template_key = ? AND version = ?", templateKey, version).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &template, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateWorkflowTemplateVersion(template *model.WorkflowTemplateVersion) error {
|
||||
return r.db.Create(template).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ProjectWorkflowInstances(projectID string) ([]model.WorkflowInstance, error) {
|
||||
var instances []model.WorkflowInstance
|
||||
err := r.db.Where("project_id = ?", projectID).Order("created_at asc").Find(&instances).Error
|
||||
return instances, err
|
||||
}
|
||||
|
||||
func (r *Repository) WorkflowInstanceForScope(projectID string, unitID string, templateVersionID string) (*model.WorkflowInstance, error) {
|
||||
var instance model.WorkflowInstance
|
||||
if err := r.db.First(&instance, "project_id = ? AND unit_id = ? AND template_version_id = ?", projectID, unitID, templateVersionID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
func (r *Repository) WorkflowInstance(id string) (*model.WorkflowInstance, error) {
|
||||
var instance model.WorkflowInstance
|
||||
if err := r.db.First(&instance, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
func (r *Repository) WorkflowSteps(instanceID string) ([]model.WorkflowStepInstance, error) {
|
||||
var steps []model.WorkflowStepInstance
|
||||
err := r.db.Where("workflow_instance_id = ?", instanceID).Order("position asc").Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func (r *Repository) NextWorkflowStep(instanceID string, position int) (*model.WorkflowStepInstance, error) {
|
||||
var step model.WorkflowStepInstance
|
||||
if err := r.db.Where("workflow_instance_id = ? AND position > ?", instanceID, position).Order("position asc").First(&step).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &step, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateWorkflowInstance(instance *model.WorkflowInstance, steps []model.WorkflowStepInstance) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(instance).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&steps).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) WorkflowStepForProject(projectID string, stepID string) (*model.WorkflowStepInstance, error) {
|
||||
var step model.WorkflowStepInstance
|
||||
err := r.db.Table("workflow_step_instances").Select("workflow_step_instances.*").Joins("JOIN workflow_instances ON workflow_instances.id = workflow_step_instances.workflow_instance_id").Where("workflow_instances.project_id = ? AND workflow_step_instances.id = ?", projectID, stepID).First(&step).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &step, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateWorkflowStep(step *model.WorkflowStepInstance) error {
|
||||
return r.db.Model(&model.WorkflowStepInstance{}).Where("id = ? AND workflow_instance_id = ?", step.ID, step.WorkflowInstanceID).Updates(map[string]any{"status": step.Status, "output_json": step.OutputJSON, "error": step.Error, "started_at": step.StartedAt, "completed_at": step.CompletedAt, "updated_at": step.UpdatedAt}).Error
|
||||
}
|
||||
|
||||
// UpdateWorkflowProgress 原子保存当前步骤、下一步骤和实例状态,确保刷新后流程依赖仍可恢复。
|
||||
func (r *Repository) UpdateWorkflowProgress(step *model.WorkflowStepInstance, next *model.WorkflowStepInstance, instance *model.WorkflowInstance, projectID string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.WorkflowStepInstance{}).Where("id = ? AND workflow_instance_id = ?", step.ID, step.WorkflowInstanceID).Updates(map[string]any{
|
||||
"status": step.Status, "output_json": step.OutputJSON, "error": step.Error, "started_at": step.StartedAt,
|
||||
"completed_at": step.CompletedAt, "updated_at": step.UpdatedAt,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if next != nil {
|
||||
if err := tx.Model(&model.WorkflowStepInstance{}).Where("id = ? AND workflow_instance_id = ?", next.ID, next.WorkflowInstanceID).
|
||||
Updates(map[string]any{"status": next.Status, "updated_at": next.UpdatedAt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&model.WorkflowInstance{}).Where("id = ? AND project_id = ?", instance.ID, projectID).
|
||||
Updates(map[string]any{"status": instance.Status, "revision": instance.Revision, "updated_at": instance.UpdatedAt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.Project{}).Where("id = ?", projectID).
|
||||
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": step.UpdatedAt}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterWorkflowTaskOutput 将成功任务、流程步骤和产物表示写入同一事务,重复回填使用任务与用途唯一键幂等。
|
||||
func (r *Repository) RegisterWorkflowTaskOutput(step *model.WorkflowStepInstance, next *model.WorkflowStepInstance, instance *model.WorkflowInstance, projectID string, link *model.WorkflowStepTask, representation *model.AssetRepresentation) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("workflow_step_id = ? AND task_id = ?", link.WorkflowStepID, link.TaskID).FirstOrCreate(link).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if representation != nil {
|
||||
if err := tx.Where("task_id = ? AND role = ?", representation.TaskID, representation.Role).FirstOrCreate(representation).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stepResult := tx.Model(&model.WorkflowStepInstance{}).Where("id = ? AND workflow_instance_id = ?", step.ID, step.WorkflowInstanceID).Updates(map[string]any{
|
||||
"status": step.Status, "output_json": step.OutputJSON, "error": step.Error, "started_at": step.StartedAt,
|
||||
"completed_at": step.CompletedAt, "updated_at": step.UpdatedAt,
|
||||
})
|
||||
if stepResult.Error != nil {
|
||||
return stepResult.Error
|
||||
}
|
||||
if stepResult.RowsAffected != 1 {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
if next != nil {
|
||||
nextResult := tx.Model(&model.WorkflowStepInstance{}).Where("id = ? AND workflow_instance_id = ?", next.ID, next.WorkflowInstanceID).Updates(map[string]any{"status": next.Status, "updated_at": next.UpdatedAt})
|
||||
if nextResult.Error != nil {
|
||||
return nextResult.Error
|
||||
}
|
||||
if nextResult.RowsAffected != 1 {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
}
|
||||
instanceResult := tx.Model(&model.WorkflowInstance{}).Where("id = ? AND project_id = ?", instance.ID, projectID).Updates(map[string]any{"status": instance.Status, "revision": instance.Revision, "updated_at": instance.UpdatedAt})
|
||||
if instanceResult.Error != nil {
|
||||
return instanceResult.Error
|
||||
}
|
||||
if instanceResult.RowsAffected != 1 {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
projectResult := tx.Model(&model.Project{}).Where("id = ?", projectID).Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": step.UpdatedAt})
|
||||
if projectResult.Error != nil {
|
||||
return projectResult.Error
|
||||
}
|
||||
if projectResult.RowsAffected != 1 {
|
||||
return gorm.ErrInvalidData
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) CanvasShareForProject(userID string, projectID string) (*model.CanvasShare, error) {
|
||||
var share model.CanvasShare
|
||||
if err := r.db.First(&share, "user_id = ? AND project_id = ?", userID, projectID).Error; err != nil {
|
||||
|
||||
Reference in new issue
Block a user