171 lines
6.7 KiB
Go
171 lines
6.7 KiB
Go
package repository
|
|
|
|
import (
|
|
"time"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
func (r *Repository) ProjectCharacterAsset(userID string, projectID string, assetID string) (*model.Asset, error) {
|
|
var asset model.Asset
|
|
err := r.db.Table("assets").Select("assets.*").
|
|
Joins("JOIN project_asset_links ON project_asset_links.asset_id = assets.id").
|
|
Where("assets.id = ? AND assets.user_id = ? AND assets.category = ? AND project_asset_links.project_id = ?", assetID, userID, model.AssetCategoryCharacter, projectID).
|
|
First(&asset).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &asset, nil
|
|
}
|
|
|
|
func (r *Repository) AssetVersion(id string) (*model.AssetVersion, error) {
|
|
var version model.AssetVersion
|
|
if err := r.db.First(&version, "id = ?", id).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &version, nil
|
|
}
|
|
|
|
func (r *Repository) AssetRepresentations(assetVersionID string) ([]model.AssetRepresentation, error) {
|
|
var representations []model.AssetRepresentation
|
|
err := r.db.Where("asset_version_id = ?", assetVersionID).Order("role asc, created_at asc").Find(&representations).Error
|
|
return representations, err
|
|
}
|
|
|
|
func (r *Repository) AssetRepresentationsForTask(taskID string) ([]model.AssetRepresentation, error) {
|
|
var representations []model.AssetRepresentation
|
|
err := r.db.Where("task_id = ?", taskID).Order("role asc, created_at asc").Find(&representations).Error
|
|
return representations, err
|
|
}
|
|
|
|
func (r *Repository) UnboundCharacterTurnaroundTasks(userID string, projectID string) ([]model.Task, error) {
|
|
var tasks []model.Task
|
|
err := r.db.Where("user_id = ? AND project_id = ? AND status = ? AND type = ?", userID, projectID, model.TaskStatusSucceeded, "canvas_image").
|
|
Where("input_json LIKE ?", "%character_turnaround%").
|
|
Where("NOT EXISTS (SELECT 1 FROM asset_representations WHERE asset_representations.task_id = tasks.id)").
|
|
Order("created_at asc").Limit(100).Find(&tasks).Error
|
|
return tasks, err
|
|
}
|
|
|
|
func (r *Repository) CharacterVoiceBinding(assetVersionID string) (*model.CharacterVoiceBinding, error) {
|
|
var binding model.CharacterVoiceBinding
|
|
if err := r.db.First(&binding, "asset_version_id = ?", assetVersionID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &binding, nil
|
|
}
|
|
|
|
func (r *Repository) VoiceProfileForUser(userID string, id string) (*model.VoiceProfile, error) {
|
|
var profile model.VoiceProfile
|
|
if err := r.db.First(&profile, "id = ? AND user_id = ?", id, userID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &profile, nil
|
|
}
|
|
|
|
func (r *Repository) VoiceProfiles(userID string) ([]model.VoiceProfile, error) {
|
|
var profiles []model.VoiceProfile
|
|
err := r.db.Where("user_id = ? AND status = ?", userID, "active").Order("created_at asc").Find(&profiles).Error
|
|
return profiles, err
|
|
}
|
|
|
|
func (r *Repository) EnsureVoiceProfiles(profiles []model.VoiceProfile) error {
|
|
if len(profiles) == 0 {
|
|
return nil
|
|
}
|
|
return r.db.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "user_id"}, {Name: "provider"}, {Name: "voice_key"}},
|
|
DoNothing: true,
|
|
}).Create(&profiles).Error
|
|
}
|
|
|
|
func (r *Repository) CreateVoiceProfile(profile *model.VoiceProfile) error {
|
|
return r.db.Create(profile).Error
|
|
}
|
|
|
|
func (r *Repository) VoiceProfileBySampleResource(userID string, resourceID string) (*model.VoiceProfile, error) {
|
|
var profile model.VoiceProfile
|
|
if err := r.db.First(&profile, "user_id = ? AND sample_resource_id = ? AND status = ?", userID, resourceID, "active").Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &profile, nil
|
|
}
|
|
|
|
// CreateProjectCharacter 将角色身份、首版本和项目关联放入同一事务。
|
|
func (r *Repository) CreateProjectCharacter(projectID string, asset *model.Asset, version *model.AssetVersion, link *model.ProjectAssetLink) error {
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(asset).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Create(version).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Create(link).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&model.Project{}).Where("id = ?", projectID).
|
|
Updates(map[string]any{"revision": gorm.Expr("revision + 1"), "updated_at": asset.UpdatedAt}).Error
|
|
})
|
|
}
|
|
|
|
// SaveCharacterVersion 通过不可变版本替换角色当前状态,旧镜头仍可继续引用历史版本。
|
|
func (r *Repository) SaveCharacterVersion(projectID string, asset *model.Asset, version *model.AssetVersion, representations []model.AssetRepresentation, voice *model.CharacterVoiceBinding) error {
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := saveCharacterVersion(tx, asset, version, representations, voice); 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
|
|
})
|
|
}
|
|
|
|
// ConfirmProjectCharacterCandidate 保证角色归并版本与候选确认同时生效,失败时不会留下孤立版本。
|
|
func (r *Repository) ConfirmProjectCharacterCandidate(candidate *model.ProjectAssetCandidate, asset *model.Asset, version *model.AssetVersion, representations []model.AssetRepresentation, voice *model.CharacterVoiceBinding) error {
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := saveCharacterVersion(tx, asset, version, representations, voice); 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 saveCharacterVersion(tx *gorm.DB, asset *model.Asset, version *model.AssetVersion, representations []model.AssetRepresentation, voice *model.CharacterVoiceBinding) error {
|
|
if err := tx.Create(version).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(representations) > 0 {
|
|
if err := tx.Create(&representations).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if voice != nil {
|
|
if err := tx.Create(voice).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
result := tx.Model(&model.Asset{}).Where("id = ? AND user_id = ?", asset.ID, asset.UserID).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 {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected != 1 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|