1ff6e494c0
Build backend images / build content-svc (push) Failing after 19s
Build backend images / build file-svc (push) Failing after 1m53s
Build backend images / build gateway (push) Failing after 16s
Build backend images / build identity-svc (push) Failing after 7m1s
Build backend images / build notification-svc (push) Failing after 7m24s
Build backend images / build render-svc (push) Failing after 3m12s
Build backend images / build studio-svc (push) Failing after 43s
feat: AE template scanner + scene editor + AEP bundle pipeline
Scene editor (admin): per-project Scenes / Shared Colors / Color Presets
manager (ProjectScenes) reachable from each project.
AEP bundle pipeline: upload .aep or .zip → stored once per template at
templates/{project_id}/(bundle.zip|template.aep); render claim probes and
returns is_bundle+md5; node-agent extracts the bundle, locates the .aep
(zip-slip guarded), and caches by md5 so repeated renders extract once.
AE template scanner ("read scenes/colours/configs from the AEP"):
- content-svc importer: POST /v1/projects/{id}/scan/{preview,apply} —
review-diff-then-merge into scenes/elements/colours (manual edits kept).
- render-svc Go quick-scan: stdlib RIFX parser extracts comp names+durations
(no AE) → POST /v1/template-scans/{id}/quick.
- render-svc AE scan jobs + node-agent runner: queue → node runs scan.jsx
(reverse of legacy JSXGenerator conventions: frfinal/frshare/frl_/frd_) →
posts ScanResult back. Migration 26_render_scan_jobs.
- admin UI: "اسکن از افترافکت" with quick/full engines + diff-review modal.
Verified: importer preview/apply, Go quick-scan end-to-end (synthetic .aep →
scene imported), bundle extract unit tests, RIFX parser unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
464 lines
19 KiB
Go
464 lines
19 KiB
Go
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"`
|
|
}
|