package model
import (
"html"
"regexp"
"strings"
"time"
"unicode/utf8"
)
var projectUnitHTMLTagPattern = regexp.MustCompile(`<[^>]+>`)
const ProjectUnitTitleMaxRunes = 240
func ProjectUnitWordCount(sourceText string) int {
plainText := projectUnitHTMLTagPattern.ReplaceAllString(sourceText, "")
return utf8.RuneCountInString(strings.TrimSpace(html.UnescapeString(plainText)))
}
func NormalizeProjectUnitTitle(value string) string {
return strings.TrimSpace(value)
}
func ValidProjectUnitTitle(value string) bool {
title := NormalizeProjectUnitTitle(value)
return title != "" && utf8.RuneCountInString(title) <= ProjectUnitTitleMaxRunes
}
const AssetIDMaxLength = 80
type Resource struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_resources_user_created,priority:1;uniqueIndex:idx_resources_user_upload_key,priority:1"`
Kind string `json:"kind" gorm:"index;size:24"`
Status ResourceStatus `json:"status" gorm:"index;size:24"`
Provider string `json:"provider" gorm:"size:24"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket" gorm:"size:160"`
// 用户 OSS 每次修改都会生成新版本,资源固定引用创建时的存储与密钥;只有同一存储位置才可复用当前 CDN。
StorageSettingID string `json:"-" gorm:"index;size:36"`
ObjectKey string `json:"objectKey" gorm:"index"`
PublicURL string `json:"publicUrl"`
MimeType string `json:"mimeType" gorm:"size:120"`
Size int64 `json:"size"`
Width int `json:"width"`
Height int `json:"height"`
DurationMs int64 `json:"durationMs"`
ETag string `json:"etag" gorm:"size:160"`
// 浏览器兼容播放副本(HEVC/H.265 原片在 Chrome 等无法解码,由 ffmpeg 转 H.264):
// PlaybackStatus: none|processing|ready|failed;PlaybackObjectKey 为本地播放目录下的文件名。
PlaybackStatus string `json:"playbackStatus" gorm:"index;size:24"`
PlaybackObjectKey string `json:"playbackObjectKey"`
PlaybackError string `json:"playbackError" gorm:"type:text"`
// UploadKey 是客户端逻辑上传身份的摘要;NULL 表示不参与幂等约束。
UploadKey *string `json:"-" gorm:"size:64;uniqueIndex:idx_resources_user_upload_key,priority:2"`
Error string `json:"error"`
CreatedAt time.Time `json:"createdAt" gorm:"index:idx_resources_user_created,priority:2"`
UpdatedAt time.Time `json:"updatedAt"`
}
// ResourceDeletionJob is the durable handoff between database deletion and
// physical object cleanup. Storage fields are frozen because the Resource row
// is removed in the same transaction that creates this job.
type ResourceDeletionJob struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36"`
ResourceID string `json:"resourceId" gorm:"index;size:36"`
Provider string `json:"provider" gorm:"size:24"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket" gorm:"size:160"`
StorageSettingID string `json:"-" gorm:"index;size:36"`
ObjectKey string `json:"objectKey" gorm:"index"`
Status ResourceDeletionStatus `json:"status" gorm:"index:idx_resource_deletion_jobs_due,priority:1;size:24"`
Attempts int `json:"attempts"`
LastError string `json:"lastError" gorm:"type:text"`
NextAttemptAt time.Time `json:"nextAttemptAt" gorm:"index:idx_resource_deletion_jobs_due,priority:2"`
LeaseOwner string `json:"-" gorm:"index;size:120"`
LeaseExpiresAt *time.Time `json:"-" gorm:"index"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// AnnouncementImageDraft marks an uploaded image as temporary until an
// announcement create or update transaction consumes it.
type AnnouncementImageDraft struct {
ResourceID string `json:"resourceId" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36"`
CreatedAt time.Time `json:"createdAt" gorm:"index"`
}
type Asset struct {
ID string `json:"id" gorm:"primaryKey;size:80"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_assets_user_updated,priority:1"`
FolderID string `json:"folderId,omitempty" gorm:"index;size:36"`
Kind string `json:"kind" gorm:"index;size:24"`
Category AssetCategory `json:"category" gorm:"index;size:32"`
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
PrimaryVersionID string `json:"primaryVersionId,omitempty" gorm:"index;size:36"`
Title string `json:"title" gorm:"size:240"`
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_assets_user_updated,priority:2"`
}
// AssetFolder 是用户素材库的一层自定义分类;业务分类仍由 Asset.Category 表达。
type AssetFolder struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_asset_folders_user_name,priority:1"`
Name string `json:"name" gorm:"size:80"`
NameKey string `json:"-" gorm:"size:80;uniqueIndex:idx_asset_folders_user_name,priority:2"`
Position int `json:"position" gorm:"index"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type ProjectAssetLink struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_project_asset_links_unique,priority:1;index:idx_project_asset_links_project_folder_position,priority:1"`
AssetID string `json:"assetId" gorm:"index;size:80;uniqueIndex:idx_project_asset_links_unique,priority:2"`
FolderID string `json:"folderId,omitempty" gorm:"index;size:36;index:idx_project_asset_links_project_folder_position,priority:2"`
Position int `json:"position" gorm:"index;index:idx_project_asset_links_project_folder_position,priority:3"`
CreatedAt time.Time `json:"createdAt"`
}
// ProjectAssetFolder 只保存项目内的目录结构;真实媒体仍由 Asset/Resource 唯一持有。
type ProjectAssetFolder struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_project_asset_folders_sibling_name,priority:1"`
ParentID string `json:"parentId,omitempty" gorm:"index;size:36;uniqueIndex:idx_project_asset_folders_sibling_name,priority:2"`
Name string `json:"name" gorm:"size:240"`
NameKey string `json:"-" gorm:"size:240;uniqueIndex:idx_project_asset_folders_sibling_name,priority:3"`
Style string `json:"style" gorm:"size:24"`
Theme string `json:"theme" gorm:"size:24"`
Position int `json:"position" gorm:"index"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type ProjectAssetCandidate struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;index:idx_project_asset_candidates_project_unit_status,priority:1;index:idx_project_asset_candidates_project_status_category,priority:1"`
UnitID string `json:"unitId,omitempty" gorm:"index;size:36;index:idx_project_asset_candidates_project_unit_status,priority:2"`
ShotID string `json:"shotId,omitempty" gorm:"index;size:36"`
Name string `json:"name" gorm:"size:240"`
NameKey string `json:"-" gorm:"index;size:240"`
Category AssetCategory `json:"category" gorm:"index;size:32;index:idx_project_asset_candidates_project_status_category,priority:3"`
Status string `json:"status" gorm:"index;size:32;index:idx_project_asset_candidates_project_unit_status,priority:3;index:idx_project_asset_candidates_project_status_category,priority:2"`
Source string `json:"source,omitempty" gorm:"index;size:48"`
DetailsJSON string `json:"detailsJson" gorm:"type:text"`
ResolvedAssetID string `json:"resolvedAssetId,omitempty" gorm:"index;size:80"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AssetVersion struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
AssetID string `json:"assetId" gorm:"index;size:80;uniqueIndex:idx_asset_versions_number,priority:1"`
Version int `json:"version" gorm:"uniqueIndex:idx_asset_versions_number,priority:2"`
Status AssetVersionStatus `json:"status" gorm:"index;size:24"`
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
Prompt string `json:"prompt" gorm:"type:text"`
Note string `json:"note" gorm:"size:500"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AssetRepresentation struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36;uniqueIndex:idx_asset_representations_task_role,priority:1"`
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_asset_representations_version_role,priority:1"`
ResourceID string `json:"resourceId,omitempty" gorm:"index;size:36"`
MediaType string `json:"mediaType" gorm:"index;size:24"`
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_asset_representations_task_role,priority:2;uniqueIndex:idx_asset_representations_version_role,priority:2"`
MetadataJSON string `json:"metadataJson" gorm:"type:text"`
CreatedAt time.Time `json:"createdAt"`
}
// VoiceProfile 是可复用的声音身份;试听音频只是表现资源,不等同于声音本身。
type VoiceProfile struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_voice_profiles_user_provider_key,priority:1"`
Name string `json:"name" gorm:"size:160"`
Provider string `json:"provider" gorm:"size:48;uniqueIndex:idx_voice_profiles_user_provider_key,priority:2"`
VoiceKey string `json:"voiceKey" gorm:"size:160;uniqueIndex:idx_voice_profiles_user_provider_key,priority:3"`
Language string `json:"language" gorm:"size:80"`
Timbre string `json:"timbre" gorm:"size:240"`
SampleResourceID string `json:"sampleResourceId,omitempty" gorm:"index;size:36"`
CompatibleModelsJSON string `json:"compatibleModelsJson" gorm:"type:text"`
Status string `json:"status" gorm:"index;size:24"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type CharacterVoiceBinding struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
AssetVersionID string `json:"assetVersionId" gorm:"uniqueIndex;size:36"`
VoiceProfileID string `json:"voiceProfileId" gorm:"index;size:36"`
Instructions string `json:"instructions" gorm:"type:text"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// Project 是短剧领域聚合根;CanvasProject 仍代表可独立创作的画布文档。
type Project struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_projects_user_name,priority:1"`
Name string `json:"name" gorm:"size:240;uniqueIndex:idx_projects_user_name,priority:2"`
Type string `json:"type" gorm:"size:32;index"`
AspectRatio string `json:"aspectRatio" gorm:"size:16"`
SourceType string `json:"sourceType" gorm:"size:32"`
Description string `json:"description" gorm:"type:text"`
CoverResourceID string `json:"coverResourceId,omitempty" gorm:"index;size:36"`
StylePresetID string `json:"stylePresetId" gorm:"size:64"`
StyleProfileJSON string `json:"styleProfileJson" gorm:"type:text"`
DefaultImageModel string `json:"defaultImageModel,omitempty" gorm:"size:500"`
DefaultVideoModel string `json:"defaultVideoModel,omitempty" gorm:"size:500"`
Status ProjectStatus `json:"status" gorm:"index;size:24"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
}
// StyleProfile 是用户可持续编辑的风格源;项目只保存应用当时的 JSON 快照,避免源对象更新污染历史生成。
type StyleProfile struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_style_profiles_user_updated,priority:1"`
Name string `json:"name" gorm:"size:160"`
Description string `json:"description" gorm:"size:500"`
CoverURL string `json:"coverUrl" gorm:"type:text"`
TagsJSON string `json:"tagsJson" gorm:"type:text"`
ProfileJSON string `json:"profileJson" gorm:"type:text"`
Favorite bool `json:"favorite" gorm:"index"`
LastUsedAt *time.Time `json:"lastUsedAt,omitempty" gorm:"index"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_style_profiles_user_updated,priority:2"`
}
type ProjectUnit struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;index:idx_project_units_project_position,priority:1"`
ParentID string `json:"parentId,omitempty" gorm:"index;size:36"`
Kind ProjectUnitKind `json:"kind" gorm:"index;size:24"`
Title string `json:"title" gorm:"size:240"`
SourceText string `json:"sourceText" gorm:"type:text"`
WordCount int `json:"wordCount"`
Status ProjectUnitStatus `json:"status" gorm:"index;size:24"`
Position int `json:"position" gorm:"index:idx_project_units_project_position,priority:2"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type CanvasUnitLink struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:1;index:idx_canvas_unit_links_project_unit,priority:1"`
CanvasID string `json:"canvasId" gorm:"index;size:80;uniqueIndex:idx_canvas_unit_links_unique,priority:2"`
UnitID string `json:"unitId" gorm:"index;size:36;uniqueIndex:idx_canvas_unit_links_unique,priority:3;index:idx_canvas_unit_links_project_unit,priority:2"`
Role string `json:"role" gorm:"size:32"`
CreatedAt time.Time `json:"createdAt"`
}
type Shot struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;index:idx_shots_project_unit_position,priority:1"`
UnitID string `json:"unitId" gorm:"index;size:36;index:idx_shots_project_unit_position,priority:2"`
CurrentRevisionID string `json:"currentRevisionId,omitempty" gorm:"index;size:36"`
Title string `json:"title" gorm:"size:240"`
Description string `json:"description" gorm:"type:text"`
Position int `json:"position" gorm:"index:idx_shots_project_unit_position,priority:3"`
DurationMs int64 `json:"durationMs"`
Status string `json:"status" gorm:"index;size:24"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// ShotRevision 保存可复现的分镜脚本版本;Shot 只保留稳定身份、排序和当前版本指针。
type ShotRevision struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ShotID string `json:"shotId" gorm:"index;size:36;uniqueIndex:idx_shot_revisions_version,priority:1"`
Version int `json:"version" gorm:"uniqueIndex:idx_shot_revisions_version,priority:2"`
PlotDescription string `json:"plotDescription" gorm:"type:text"`
Action string `json:"action" gorm:"type:text"`
Dialogue string `json:"dialogue" gorm:"type:text"`
ShotSize string `json:"shotSize" gorm:"size:80"`
CameraAngle string `json:"cameraAngle" gorm:"size:80"`
CameraMovement string `json:"cameraMovement" gorm:"size:120"`
DurationMs int64 `json:"durationMs"`
ImagePrompt string `json:"imagePrompt" gorm:"type:text"`
VideoPrompt string `json:"videoPrompt" gorm:"type:text"`
NegativePrompt string `json:"negativePrompt" gorm:"type:text"`
ContinuityNotes string `json:"continuityNotes" gorm:"type:text"`
ActionBeatsJSON string `json:"actionBeatsJson" gorm:"type:text"`
CreatedBy string `json:"createdBy,omitempty" gorm:"index;size:36"`
CreatedAt time.Time `json:"createdAt"`
}
// ShotArtifact 是镜头的版本化生产产物。修改分镜或资产引用时只标记 stale,不删除历史。
type ShotArtifact struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;index:idx_shot_artifacts_project_unit,priority:1"`
UnitID string `json:"unitId" gorm:"index;size:36;index:idx_shot_artifacts_project_unit,priority:2"`
ShotID string `json:"shotId" gorm:"index;size:36;uniqueIndex:idx_shot_artifacts_version,priority:1"`
RevisionID string `json:"revisionId,omitempty" gorm:"index;size:36"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
Type string `json:"type" gorm:"index;size:40;uniqueIndex:idx_shot_artifacts_version,priority:2"`
Version int `json:"version" gorm:"uniqueIndex:idx_shot_artifacts_version,priority:3"`
ResourceID string `json:"resourceId,omitempty" gorm:"index;size:36"`
Status string `json:"status" gorm:"index;size:24"`
Selected bool `json:"selected" gorm:"index"`
MetadataJSON string `json:"metadataJson" gorm:"type:text"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type ShotAssetReference struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ShotID string `json:"shotId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:1"`
AssetVersionID string `json:"assetVersionId" gorm:"index;size:36;uniqueIndex:idx_shot_asset_reference_unique,priority:2"`
Role string `json:"role" gorm:"index;size:32;uniqueIndex:idx_shot_asset_reference_unique,priority:3"`
Status string `json:"status" gorm:"index;size:24"`
CreatedAt time.Time `json:"createdAt"`
}
type WorkflowTemplateVersion struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
TemplateKey string `json:"templateKey" gorm:"size:80;uniqueIndex:idx_workflow_template_version,priority:1"`
Name string `json:"name" gorm:"size:160"`
Version int `json:"version" gorm:"uniqueIndex:idx_workflow_template_version,priority:2"`
DefinitionJSON string `json:"definitionJson" gorm:"type:text"`
CreatedAt time.Time `json:"createdAt"`
}
type WorkflowInstance struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ProjectID string `json:"projectId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:1"`
UnitID string `json:"unitId,omitempty" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:2"`
TemplateVersionID string `json:"templateVersionId" gorm:"index;size:36;uniqueIndex:idx_workflow_instance_scope,priority:3"`
Scope string `json:"scope" gorm:"index;size:24"`
Status WorkflowStatus `json:"status" gorm:"index;size:24"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type WorkflowStepInstance struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
WorkflowInstanceID string `json:"workflowInstanceId" gorm:"index;size:36;uniqueIndex:idx_workflow_steps_instance_key,priority:1"`
StepKey string `json:"stepKey" gorm:"size:80;uniqueIndex:idx_workflow_steps_instance_key,priority:2"`
Name string `json:"name" gorm:"size:160"`
Position int `json:"position"`
Status WorkflowStepStatus `json:"status" gorm:"index;size:24"`
InputJSON string `json:"inputJson" gorm:"type:text"`
OutputJSON string `json:"outputJson" gorm:"type:text"`
Error string `json:"error" gorm:"type:text"`
StartedAt *time.Time `json:"startedAt"`
CompletedAt *time.Time `json:"completedAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type WorkflowStepTask struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
WorkflowStepID string `json:"workflowStepId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:1"`
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_workflow_step_tasks_unique,priority:2"`
CreatedAt time.Time `json:"createdAt"`
}
// ProductionTaskLink 显式区分领域项目、画布和镜头上下文,避免继续复用 Task.ProjectID 表达多种身份。
type ProductionTaskLink struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
TaskID string `json:"taskId" gorm:"index;size:36;uniqueIndex:idx_production_task_context,priority:1"`
ProjectID string `json:"projectId" gorm:"index;size:36"`
CanvasID string `json:"canvasId,omitempty" gorm:"index;size:80"`
UnitID string `json:"unitId,omitempty" gorm:"index;size:36"`
ShotID string `json:"shotId,omitempty" gorm:"index;size:36;uniqueIndex:idx_production_task_context,priority:2"`
WorkflowStepID string `json:"workflowStepId,omitempty" gorm:"index;size:36"`
ArtifactType string `json:"artifactType,omitempty" gorm:"index;size:40;uniqueIndex:idx_production_task_context,priority:3"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type CanvasProject struct {
ID string `json:"id" gorm:"primaryKey;size:80"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_canvas_projects_user_updated,priority:1;index:idx_canvas_projects_user_project_updated,priority:1"`
ProjectID string `json:"projectId,omitempty" gorm:"index;size:36;index:idx_canvas_projects_user_project_updated,priority:2"`
Title string `json:"title" gorm:"size:240"`
PayloadJSON string `json:"payloadJson" gorm:"type:text"`
Revision int64 `json:"revision" gorm:"not null;default:1"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index:idx_canvas_projects_user_updated,priority:2;index:idx_canvas_projects_user_project_updated,priority:3"`
}
type CanvasShare struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_canvas_share_owner_project,priority:1"`
ProjectID string `json:"projectId" gorm:"index;size:80;uniqueIndex:idx_canvas_share_owner_project,priority:2"`
TokenHash string `json:"-" gorm:"uniqueIndex;size:64"`
TokenCipher string `json:"-" gorm:"type:text"`
Enabled bool `json:"enabled" gorm:"index"`
ExpiresAt *time.Time `json:"expiresAt" gorm:"index"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type PromptTemplate struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_prompt_template_operation_version,priority:1"`
Name string `json:"name" gorm:"size:120"`
Version int `json:"version" gorm:"uniqueIndex:idx_prompt_template_operation_version,priority:2"`
Content string `json:"content" gorm:"type:text"`
OutputType string `json:"outputType" gorm:"size:24"`
Enabled bool `json:"enabled" gorm:"index;index:idx_prompt_template_active,priority:2"`
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// UserPromptCustomization 只保存用户的创作策略层,动态上下文和输出契约始终由服务端编译器注入。
type UserPromptCustomization struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_user_prompt_operation,priority:1"`
Operation string `json:"operation" gorm:"size:64;index;uniqueIndex:idx_user_prompt_operation,priority:2"`
Mode string `json:"mode" gorm:"size:24"`
Content string `json:"content" gorm:"type:text"`
BaseTemplateID string `json:"baseTemplateId" gorm:"size:36;index"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type Announcement struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
Title string `json:"title" gorm:"size:120"`
Content string `json:"content" gorm:"type:text"`
ImageResourceID string `json:"imageResourceId,omitempty" gorm:"index;size:36"`
ImageURL string `json:"imageUrl,omitempty" gorm:"-"`
Level AnnouncementLevel `json:"level" gorm:"index;size:24"`
Pinned bool `json:"pinned" gorm:"index"`
Status AnnouncementStatus `json:"status" gorm:"index;size:24;index:idx_announcements_status_published,priority:1"`
CreatedBy string `json:"createdBy" gorm:"index;size:36"`
PublishedAt time.Time `json:"publishedAt" gorm:"index:idx_announcements_status_published,priority:2"`
ClosedAt *time.Time `json:"closedAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type UserAnnouncementRead struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:1"`
AnnouncementID string `json:"announcementId" gorm:"index;size:36;uniqueIndex:idx_user_announcement_read,priority:2"`
ReadAt time.Time `json:"readAt"`
}
// BannerTitleRun 是通知标题中一段连续文本的样式覆盖;零值字段表示沿用默认样式。
// 字重与字号用指针区分「未设置」和「显式设置为默认值」。
type BannerTitleRun struct {
Text string `json:"text"`
FontSize *int `json:"fontSize,omitempty"` // px,仅接受 10-20
FontWeight *int `json:"fontWeight,omitempty"` // 仅接受 400 / 500 / 600 / 700
FontFamily string `json:"fontFamily,omitempty"` // "" | "sans" | "serif" | "mono"
Color string `json:"color,omitempty"` // #RRGGBB
}
type BannerAnnouncement struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
Title string `json:"title" gorm:"size:120"` // 纯文本标题,由 TitleRuns 拼接得出,供列表展示和关键字检索
// TitleRuns 是标题的样式分段,前端按段渲染;持久化在 title_runs 文本列,由仓储层显式编解码。
TitleRuns []BannerTitleRun `json:"titleRuns,omitempty" gorm:"-"`
TitleRunsJSON string `json:"-" gorm:"column:title_runs;type:text"`
// NoticeType 决定通知条底色("notice" | "activity" | "update" | "warning"),取值白名单见 app 层。
NoticeType string `json:"noticeType" gorm:"size:24"`
Link string `json:"link" gorm:"size:500"` // 点击跳转目标:http(s) 外链或 / 开头的站内路径;空表示不可点击
Status string `json:"status" gorm:"size:24;index"` // "active" | "disabled"
StartsAt *time.Time `json:"startsAt,omitempty" gorm:"index"`
EndsAt *time.Time `json:"endsAt,omitempty" gorm:"index"`
CreatedBy string `json:"createdBy" gorm:"size:36"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}