package models import ( "time" "github.com/google/uuid" ) // ── Enums ──────────────────────────────────────────────────────────────────── const ( // NodeStatus NodeStatusReady = "Ready" NodeStatusBusy = "Busy" NodeStatusOffline = "Offline" NodeStatusMaintenance = "Maintenance" NodeStatusCrashed = "Crashed" NodeStatusUpdating = "Updating" NodeStatusDisabled = "Disabled" // NodeKind NodeKindShared = "Shared" NodeKindDedicated = "Dedicated" NodeKindSpot = "Spot" // RenderStep StepQueued = "Queued" StepPreparing = "Preparing" StepTemplateCache = "TemplateCache" StepJsxGen = "JsxGen" StepMusic = "Music" StepRendering = "Rendering" StepValidating = "Validating" StepRepairing = "Repairing" StepOptimisation = "Optimisation" StepVideo = "Video" StepMixing = "Mixing" StepFinal = "Final" StepUploading = "Uploading" StepDone = "Done" StepFailed = "Failed" StepCancelled = "Cancelled" // PriceKind PriceKindFree = "Free" PriceKindPreview = "Preview" PriceKindCash = "Cash" PriceKindPlan = "Plan" PriceKindSnapshot = "Snapshot" PriceKindReseller = "Reseller" // RenderQuality QualityLow = "Low" QualityMedium = "Medium" QualityHigh = "High" QualityFull = "Full" QualityLossless = "Lossless" // FrameJobStatus FrameJobPending = "Pending" FrameJobRendering = "Rendering" FrameJobValidated = "Validated" FrameJobRepairing = "Repairing" FrameJobConverting = "Converting" FrameJobDone = "Done" FrameJobFailed = "Failed" // PriorityQueue QueueSnapshot = "snapshot" QueueVIP = "vip" QueuePaid = "paid" QueuePreview = "preview" QueueMockup = "mockup" QueueVoiceover = "voiceover" // Export types ExportCreateRender = "Render" ExportCreateUpload = "Upload" ExportCreateSnapshot = "Snapshot" ExportCreateReupload = "Reupload" ExportFileVideo = "Video" ExportFileImage = "Image" ExportFileAudio = "Audio" ExportFileGIF = "GIF" ExportFilePDF = "PDF" ) // ── Domain entities ────────────────────────────────────────────────────────── type RenderNode struct { ID uuid.UUID `json:"id"` Name string `json:"name"` Region string `json:"region"` NodeIP string `json:"node_ip"` WorkerPort int `json:"worker_port"` PublicEndpoint *string `json:"public_endpoint,omitempty"` RamGB *int `json:"ram_gb,omitempty"` CPUCores *int `json:"cpu_cores,omitempty"` GPUModel *string `json:"gpu_model,omitempty"` StorageGB *int `json:"storage_gb,omitempty"` CurrentAEVersion string `json:"current_ae_version"` AvailableAEVersions []string `json:"available_ae_versions"` NodeAgentVersion *string `json:"node_agent_version,omitempty"` NodeKind string `json:"node_kind"` OwnerUserID *uuid.UUID `json:"owner_user_id,omitempty"` OwnerTenantID *uuid.UUID `json:"owner_tenant_id,omitempty"` Status string `json:"status"` CurrentJobID *uuid.UUID `json:"current_job_id,omitempty"` CurrentFrameJobID *uuid.UUID `json:"current_frame_job_id,omitempty"` JobStartedAt *time.Time `json:"job_started_at,omitempty"` LastHeartbeatAt *time.Time `json:"last_heartbeat_at,omitempty"` LastCPUPct *int `json:"last_cpu_pct,omitempty"` LastRAMAvailableMB *int `json:"last_ram_available_mb,omitempty"` AERunning bool `json:"ae_running"` LifetimeTaskCount int64 `json:"lifetime_task_count"` LifetimeCrashCount int `json:"lifetime_crash_count"` ConsecutiveFailures int `json:"consecutive_failures"` Priority int `json:"priority"` IsActive bool `json:"is_active"` AcceptsNewJobs bool `json:"accepts_new_jobs"` LastMaintenanceAt *time.Time `json:"last_maintenance_at,omitempty"` NextMaintenanceAt *time.Time `json:"next_maintenance_at,omitempty"` MaintenanceReason *string `json:"maintenance_reason,omitempty"` CachedTemplateMD5s []string `json:"cached_template_md5s"` CacheUsedGB int `json:"cache_used_gb"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type RenderJob struct { ID uuid.UUID `json:"id"` TenantID uuid.UUID `json:"tenant_id"` UserID uuid.UUID `json:"user_id"` SavedProjectID uuid.UUID `json:"saved_project_id"` OriginalProjectID uuid.UUID `json:"original_project_id"` ProjectName *string `json:"project_name,omitempty"` Title *string `json:"title,omitempty"` Name *string `json:"name,omitempty"` ExternalJobID *string `json:"external_job_id,omitempty"` PriorityQueue string `json:"priority_queue"` PriorityScore int `json:"priority_score"` Step string `json:"step"` RenderProgress int `json:"render_progress"` ConvertProgress int `json:"convert_progress"` ImagePreviewB64 *string `json:"image_preview_b64,omitempty"` PriceType string `json:"price_type"` PaidPriceMinor int64 `json:"paid_price_minor"` DiscountCode *string `json:"discount_code,omitempty"` SupportFlatrender bool `json:"support_flatrender"` Mode string `json:"mode"` Quality string `json:"quality"` Resolution string `json:"resolution"` RHeight int `json:"r_height"` FrameRate int `json:"frame_rate"` Is60FPS bool `json:"is_60_fps"` DurationSec float64 `json:"duration_sec"` ExportDurationSec *float64 `json:"export_duration_sec,omitempty"` HasMusic bool `json:"has_music"` HasSFX bool `json:"has_sfx"` HasVoiceover bool `json:"has_voiceover"` MusicVolume *float64 `json:"music_volume,omitempty"` SFXVolume *float64 `json:"sfx_volume,omitempty"` VoiceoverVolume *float64 `json:"voiceover_volume,omitempty"` RenderNodeCount int `json:"render_node_count"` CurrentActiveNodes int `json:"current_active_nodes"` Region *string `json:"region,omitempty"` TellMeWhenDone bool `json:"tell_me_when_done"` RetryCount int `json:"retry_count"` MaxRetries int `json:"max_retries"` RepairAttempts int `json:"repair_attempts"` FailedMessage *string `json:"failed_message,omitempty"` FailedAtStep *string `json:"failed_at_step,omitempty"` ExportID *uuid.UUID `json:"export_id,omitempty"` TaskStartDate time.Time `json:"task_start_date"` QueuedAt time.Time `json:"queued_at"` StartedAt *time.Time `json:"started_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type FrameJob struct { ID uuid.UUID `json:"id"` RenderJobID uuid.UUID `json:"render_job_id"` NodeID uuid.UUID `json:"node_id"` StartFrame int `json:"start_frame"` EndFrame int `json:"end_frame"` CollectFrameCount int `json:"collect_frame_count"` OrderValue int `json:"order_value"` FolderName string `json:"folder_name"` ConvertURL *string `json:"convert_url,omitempty"` Status string `json:"status"` FramesRendered int `json:"frames_rendered"` FramesValidated int `json:"frames_validated"` Attempt int `json:"attempt"` LastError *string `json:"last_error,omitempty"` OutputMP4URL *string `json:"output_mp4_url,omitempty"` AssignedAt time.Time `json:"assigned_at"` StartedAt *time.Time `json:"started_at,omitempty"` LastProgressAt *time.Time `json:"last_progress_at,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Snapshot struct { ID uuid.UUID `json:"id"` TenantID uuid.UUID `json:"tenant_id"` UserID uuid.UUID `json:"user_id"` SavedProjectID uuid.UUID `json:"saved_project_id"` SceneKey string `json:"scene_key"` FrameNumber int `json:"frame_number"` InputsHash string `json:"inputs_hash"` Status string `json:"status"` RenderNodeID *uuid.UUID `json:"render_node_id,omitempty"` ImageURL *string `json:"image_url,omitempty"` ThumbnailURL *string `json:"thumbnail_url,omitempty"` Width *int `json:"width,omitempty"` Height *int `json:"height,omitempty"` SizeBytes *int64 `json:"size_bytes,omitempty"` RequestedAt time.Time `json:"requested_at"` CompletedAt *time.Time `json:"completed_at,omitempty"` DurationMS *int `json:"duration_ms,omitempty"` ExpiresAt time.Time `json:"expires_at"` ErrorMessage *string `json:"error_message,omitempty"` CreatedAt time.Time `json:"created_at"` } type Export struct { ID uuid.UUID `json:"id"` TenantID uuid.UUID `json:"tenant_id"` UserID uuid.UUID `json:"user_id"` SavedProjectID uuid.UUID `json:"saved_project_id"` ProjectID uuid.UUID `json:"project_id"` RenderJobID *uuid.UUID `json:"render_job_id,omitempty"` Image *string `json:"image,omitempty"` Path string `json:"path"` FileExtension string `json:"file_extension"` FileType string `json:"file_type"` RenderQuality string `json:"render_quality"` CreateType string `json:"create_type"` SizeBytes int64 `json:"size_bytes"` DurationSec *float64 `json:"duration_sec,omitempty"` Width *int `json:"width,omitempty"` Height *int `json:"height,omitempty"` ProduceDate time.Time `json:"produce_date"` AutoDeleteDate time.Time `json:"auto_delete_date"` DeleteNotified bool `json:"delete_notified"` CreatedAt time.Time `json:"created_at"` DeletedAt *time.Time `json:"deleted_at,omitempty"` } type ExportFile struct { ID uuid.UUID `json:"id"` ExportID uuid.UUID `json:"export_id"` UserID uuid.UUID `json:"user_id"` Name *string `json:"name,omitempty"` Thumbnail *string `json:"thumbnail,omitempty"` Path string `json:"path"` SizeBytes int64 `json:"size_bytes"` FileType string `json:"file_type"` Width *int `json:"width,omitempty"` Height *int `json:"height,omitempty"` Sort int `json:"sort"` CreatedAt time.Time `json:"created_at"` } type NodeHealthLog struct { ID int64 `json:"id"` NodeID uuid.UUID `json:"node_id"` RecordedAt time.Time `json:"recorded_at"` Status string `json:"status"` CPUPct *int `json:"cpu_pct,omitempty"` RAMAvailableMB *int `json:"ram_available_mb,omitempty"` AERunning *bool `json:"ae_running,omitempty"` CurrentJobID *uuid.UUID `json:"current_job_id,omitempty"` CurrentFrame *int `json:"current_frame,omitempty"` CacheUsedGB *int `json:"cache_used_gb,omitempty"` } type NodeCrash struct { ID uuid.UUID `json:"id"` NodeID uuid.UUID `json:"node_id"` RenderJobID *uuid.UUID `json:"render_job_id,omitempty"` FrameJobID *uuid.UUID `json:"frame_job_id,omitempty"` CrashedAt time.Time `json:"crashed_at"` LastKnownFrame *int `json:"last_known_frame,omitempty"` CrashSignal *string `json:"crash_signal,omitempty"` ErrorLog *string `json:"error_log,omitempty"` LogFileURL *string `json:"log_file_url,omitempty"` AutoRecovered bool `json:"auto_recovered"` RecoveryAction *string `json:"recovery_action,omitempty"` RecoveredAt *time.Time `json:"recovered_at,omitempty"` CreatedAt time.Time `json:"created_at"` } type NodeUpdate struct { ID uuid.UUID `json:"id"` UpdateFileName string `json:"update_file_name"` UpdateNumber int `json:"update_number"` Description *string `json:"description,omitempty"` TargetAEVersion *string `json:"target_ae_version,omitempty"` InUpdateQueue bool `json:"in_update_queue"` RolledOutToNodeIDs []uuid.UUID `json:"rolled_out_to_node_ids"` LastUpdateQueueDate *time.Time `json:"last_update_queue_date,omitempty"` CreateDate time.Time `json:"create_date"` CreatedAt time.Time `json:"created_at"` } // ── Request / Response types ───────────────────────────────────────────────── type PagedResponse[T any] struct { Data []T `json:"data"` Meta PaginationMeta `json:"meta"` } type PaginationMeta struct { Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` HasMore bool `json:"has_more"` } type RenderJobCreateRequest struct { SavedProjectID uuid.UUID `json:"saved_project_id" binding:"required"` Quality string `json:"quality" binding:"required"` Resolution string `json:"resolution" binding:"required"` FrameRate *int `json:"frame_rate"` Is60FPS *bool `json:"is_60_fps"` PriceType *string `json:"price_type"` DiscountCode *string `json:"discount_code"` SupportFlatrender *bool `json:"support_flatrender"` TellMeWhenDone *bool `json:"tell_me_when_done"` PreferredRegion *string `json:"preferred_region"` } type SnapshotCreateRequest struct { SavedProjectID uuid.UUID `json:"saved_project_id" binding:"required"` SceneKey string `json:"scene_key" binding:"required"` FrameNumber int `json:"frame_number" binding:"min=0"` } type NodeCreateRequest struct { Name string `json:"name" binding:"required"` Region string `json:"region" binding:"required"` NodeIP string `json:"node_ip" binding:"required"` WorkerPort int `json:"worker_port" binding:"required"` CurrentAEVersion string `json:"current_ae_version" binding:"required"` RamGB *int `json:"ram_gb"` CPUCores *int `json:"cpu_cores"` NodeKind *string `json:"node_kind"` OwnerUserID *uuid.UUID `json:"owner_user_id"` Priority *int `json:"priority"` } type NodePatchRequest struct { Priority *int `json:"priority"` IsActive *bool `json:"is_active"` AcceptsNewJobs *bool `json:"accepts_new_jobs"` NodeKind *string `json:"node_kind"` OwnerUserID *uuid.UUID `json:"owner_user_id"` NextMaintenanceAt *time.Time `json:"next_maintenance_at"` MaintenanceReason *string `json:"maintenance_reason"` } type NodeHeartbeatRequest struct { NodeID uuid.UUID `json:"node_id"` Status string `json:"status"` CPUPct *int `json:"cpu_pct"` RAMAvailableMB *int `json:"ram_available_mb"` AERunning *bool `json:"ae_running"` CurrentJobID *uuid.UUID `json:"current_job_id"` CurrentFrame *int `json:"current_frame"` CacheUsedGB *int `json:"cache_used_gb"` } type NodeOnlineRequest struct { NodeAgentVersion string `json:"node_agent_version"` CurrentAEVersion string `json:"current_ae_version"` AvailableAEVersions []string `json:"available_ae_versions"` RamGB *int `json:"ram_gb"` CPUCores *int `json:"cpu_cores"` CacheUsedGB *int `json:"cache_used_gb"` CachedTemplateMD5s []string `json:"cached_template_md5s"` } type FrameProgressRequest struct { FrameJobID uuid.UUID `json:"frame_job_id" binding:"required"` FrameNumber int `json:"frame_number"` FileSizeBytes *int64 `json:"file_size_bytes"` CompletedAt *time.Time `json:"completed_at"` } type CrashReportRequest struct { NodeID uuid.UUID `json:"node_id" binding:"required"` FrameJobID *uuid.UUID `json:"frame_job_id"` LastKnownFrame *int `json:"last_known_frame"` CrashSignal *string `json:"crash_signal"` AEVersion *string `json:"ae_version"` ErrorLogTail *string `json:"error_log_tail"` LogFileURL *string `json:"log_file_url"` } type ClaimJobRequest struct { NodeID uuid.UUID `json:"node_id" binding:"required"` Region string `json:"region"` } type ClaimedJob struct { JobID uuid.UUID `json:"job_id"` SavedProjectID uuid.UUID `json:"saved_project_id"` Quality string `json:"quality"` Resolution string `json:"resolution"` FrameRate int `json:"frame_rate"` HasMusic bool `json:"has_music"` HasVoiceover bool `json:"has_voiceover"` // AEPDownloadURL is a presigned MinIO GET URL for the .aep project file // (or .zip bundle). Valid for 2 hours. Empty when the template is not yet uploaded. AEPDownloadURL string `json:"aep_download_url,omitempty"` // IsBundle is true when AEPDownloadURL points to a .zip bundle (the .aep plus // footage/fonts) that the node agent must extract before rendering. IsBundle bool `json:"is_bundle,omitempty"` // BundleMD5 is the stored object's ETag/MD5 — the node uses it as a cache key so // repeated renders of the same template download + extract the bundle only once. BundleMD5 string `json:"bundle_md5,omitempty"` } // OutputUploadURLResponse is returned by POST .../output-upload-url. type OutputUploadURLResponse struct { ExportID uuid.UUID `json:"export_id"` UploadURL string `json:"upload_url"` ObjectKey string `json:"object_key"` ExpiresAt time.Time `json:"expires_at"` } type CacheUpdateRequest struct { Action string `json:"action" binding:"required"` ProjectID *uuid.UUID `json:"project_id"` AEPFileMD5 string `json:"aep_file_md5" binding:"required"` FileSizeBytes *int64 `json:"file_size_bytes"` CacheUsedGB *int `json:"cache_used_gb"` ErrorMessage *string `json:"error_message"` } type ReplicaReadyRequest struct { NodeID uuid.UUID `json:"node_id" binding:"required"` ReplicaPath string `json:"replica_path" binding:"required"` ReplicaMD5 *string `json:"replica_md5"` } type APIError struct { Code string `json:"code"` Message string `json:"message"` } // Claims carries JWT payload type Claims struct { UserID uuid.UUID `json:"sub"` TenantID uuid.UUID `json:"tenant_id"` IsAdmin bool `json:"is_admin"` Role string `json:"role"` }