90ac0b81d1
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
248 lines
9.3 KiB
Go
248 lines
9.3 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ── Enums ─────────────────────────────────────────────────────────────────────
|
|
|
|
type FileKind string
|
|
|
|
const (
|
|
FileKindVideo FileKind = "Video"
|
|
FileKindImage FileKind = "Image"
|
|
FileKindAudio FileKind = "Audio"
|
|
FileKindVoiceover FileKind = "Voiceover"
|
|
FileKindDocument FileKind = "Document"
|
|
FileKindOther FileKind = "Other"
|
|
)
|
|
|
|
type FolderKind string
|
|
|
|
const (
|
|
FolderKindSystem FolderKind = "System"
|
|
FolderKindUser FolderKind = "User"
|
|
FolderKindShared FolderKind = "Shared"
|
|
FolderKindTenant FolderKind = "Tenant"
|
|
)
|
|
|
|
type UploadStatus string
|
|
|
|
const (
|
|
UploadStatusPending UploadStatus = "Pending"
|
|
UploadStatusUploading UploadStatus = "Uploading"
|
|
UploadStatusProcessing UploadStatus = "Processing"
|
|
UploadStatusReady UploadStatus = "Ready"
|
|
UploadStatusFailed UploadStatus = "Failed"
|
|
UploadStatusQuarantined UploadStatus = "Quarantined"
|
|
)
|
|
|
|
type CleanupEntityType string
|
|
|
|
const (
|
|
CleanupEntityExport CleanupEntityType = "Export"
|
|
CleanupEntityTempRenderFolder CleanupEntityType = "TempRenderFolder"
|
|
CleanupEntityOrphanedFile CleanupEntityType = "OrphanedFile"
|
|
CleanupEntityUnusedUpload CleanupEntityType = "UnusedUpload"
|
|
CleanupEntitySnapshotExpired CleanupEntityType = "SnapshotExpired"
|
|
)
|
|
|
|
type CleanupStatus string
|
|
|
|
const (
|
|
CleanupStatusScheduled CleanupStatus = "Scheduled"
|
|
CleanupStatusNotified CleanupStatus = "Notified"
|
|
CleanupStatusProcessing CleanupStatus = "Processing"
|
|
CleanupStatusDone CleanupStatus = "Done"
|
|
CleanupStatusSkipped CleanupStatus = "Skipped"
|
|
CleanupStatusFailed CleanupStatus = "Failed"
|
|
)
|
|
|
|
// ── Domain ────────────────────────────────────────────────────────────────────
|
|
|
|
type UserFolder struct {
|
|
ID uuid.UUID `json:"id"`
|
|
TenantID uuid.UUID `json:"tenant_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Name string `json:"name"`
|
|
FolderType FolderKind `json:"folder_type"`
|
|
ParentFolderID *uuid.UUID `json:"parent_folder_id,omitempty"`
|
|
FileCount int `json:"file_count"`
|
|
TotalSizeBytes int64 `json:"total_size_bytes"`
|
|
Sort int `json:"sort"`
|
|
IsShared bool `json:"is_shared"`
|
|
ShareToken *string `json:"share_token,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
|
}
|
|
|
|
type UserFile struct {
|
|
ID uuid.UUID `json:"id"`
|
|
TenantID uuid.UUID `json:"tenant_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
UserFolderID *uuid.UUID `json:"user_folder_id,omitempty"`
|
|
Name string `json:"name"`
|
|
OriginalFilename *string `json:"original_filename,omitempty"`
|
|
FileExtension *string `json:"file_extension,omitempty"`
|
|
MimeType *string `json:"mime_type,omitempty"`
|
|
FileType FileKind `json:"file_type"`
|
|
MinioBucket string `json:"minio_bucket"`
|
|
MinioKey string `json:"minio_key"`
|
|
CdnURL *string `json:"cdn_url,omitempty"`
|
|
FileAddress string `json:"file_address"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
Md5Hash *string `json:"md5_hash,omitempty"`
|
|
Sha256Hash *string `json:"sha256_hash,omitempty"`
|
|
DurationSec *float64 `json:"duration_sec,omitempty"`
|
|
Width *int `json:"width,omitempty"`
|
|
Height *int `json:"height,omitempty"`
|
|
Fps *float64 `json:"fps,omitempty"`
|
|
BitrateKbps *int `json:"bitrate_kbps,omitempty"`
|
|
Codec *string `json:"codec,omitempty"`
|
|
HasAudio *bool `json:"has_audio,omitempty"`
|
|
HasVideo *bool `json:"has_video,omitempty"`
|
|
ThumbnailURL *string `json:"thumbnail_url,omitempty"`
|
|
WaveformData *string `json:"waveform_data,omitempty"`
|
|
UploadStatus UploadStatus `json:"upload_status"`
|
|
UploadProgress int `json:"upload_progress"`
|
|
Source *string `json:"source,omitempty"`
|
|
ExportID *uuid.UUID `json:"export_id,omitempty"`
|
|
ParentFileID *uuid.UUID `json:"parent_file_id,omitempty"`
|
|
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
|
|
UseCount int `json:"use_count"`
|
|
IsPublic bool `json:"is_public"`
|
|
ShareToken *string `json:"share_token,omitempty"`
|
|
Metadata string `json:"metadata"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
|
}
|
|
|
|
type StorageQuota struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
TenantID uuid.UUID `json:"tenant_id"`
|
|
PlanQuotaBytes int64 `json:"plan_quota_bytes"`
|
|
BonusQuotaBytes int64 `json:"bonus_quota_bytes"`
|
|
UsedBytes int64 `json:"used_bytes"`
|
|
VideoCount int `json:"video_count"`
|
|
ImageCount int `json:"image_count"`
|
|
AudioCount int `json:"audio_count"`
|
|
VideoBytes int64 `json:"video_bytes"`
|
|
ImageBytes int64 `json:"image_bytes"`
|
|
AudioBytes int64 `json:"audio_bytes"`
|
|
Last90PctNotifiedAt *time.Time `json:"last_90pct_notified_at,omitempty"`
|
|
Last100PctNotifiedAt *time.Time `json:"last_100pct_notified_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type UploadSession struct {
|
|
ID uuid.UUID `json:"id"`
|
|
TenantID uuid.UUID `json:"tenant_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
MinioBucket string `json:"minio_bucket"`
|
|
MinioKey string `json:"minio_key"`
|
|
MinioUploadID string `json:"minio_upload_id"`
|
|
Filename string `json:"filename"`
|
|
MimeType *string `json:"mime_type,omitempty"`
|
|
TotalSizeBytes int64 `json:"total_size_bytes"`
|
|
ChunksReceived int `json:"chunks_received"`
|
|
BytesReceived int64 `json:"bytes_received"`
|
|
ChunkSizeBytes int `json:"chunk_size_bytes"`
|
|
TargetFolderID *uuid.UUID `json:"target_folder_id,omitempty"`
|
|
TargetFileID *uuid.UUID `json:"target_file_id,omitempty"`
|
|
Status UploadStatus `json:"status"`
|
|
ErrorMessage *string `json:"error_message,omitempty"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type MinioBucket struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Region string `json:"region"`
|
|
Endpoint string `json:"endpoint"`
|
|
Purpose string `json:"purpose"`
|
|
IsPublic bool `json:"is_public"`
|
|
CdnBaseURL *string `json:"cdn_base_url,omitempty"`
|
|
IsActive bool `json:"is_active"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ── Request / Response ────────────────────────────────────────────────────────
|
|
|
|
type CreateFolderRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
ParentFolderID *uuid.UUID `json:"parent_folder_id"`
|
|
}
|
|
|
|
type MoveFolderRequest struct {
|
|
ParentFolderID *uuid.UUID `json:"parent_folder_id"`
|
|
}
|
|
|
|
type RenameFolderRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
}
|
|
|
|
type InitiateUploadRequest struct {
|
|
Filename string `json:"filename" binding:"required"`
|
|
MimeType *string `json:"mime_type"`
|
|
TotalSizeBytes int64 `json:"total_size_bytes" binding:"required,min=1"`
|
|
ChunkSizeBytes int `json:"chunk_size_bytes"`
|
|
TargetFolderID *uuid.UUID `json:"target_folder_id"`
|
|
}
|
|
|
|
type PresignedUploadRequest struct {
|
|
Filename string `json:"filename" binding:"required"`
|
|
MimeType *string `json:"mime_type"`
|
|
SizeBytes int64 `json:"size_bytes" binding:"required,min=1"`
|
|
TargetFolderID *uuid.UUID `json:"target_folder_id"`
|
|
}
|
|
|
|
type PresignedUploadResponse struct {
|
|
UploadURL string `json:"upload_url"`
|
|
FileID uuid.UUID `json:"file_id"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
type FileListRequest struct {
|
|
Page int `form:"page,default=1"`
|
|
PageSize int `form:"page_size,default=20"`
|
|
FolderID *uuid.UUID `form:"folder_id"`
|
|
FileType *FileKind `form:"file_type"`
|
|
Search *string `form:"search"`
|
|
}
|
|
|
|
type PagedResponse[T any] struct {
|
|
Items []T `json:"items"`
|
|
Meta PaginationMeta `json:"meta"`
|
|
}
|
|
|
|
type PaginationMeta struct {
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
Total int64 `json:"total"`
|
|
TotalPages int `json:"total_pages"`
|
|
}
|
|
|
|
type APIError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type ErrorResponse struct {
|
|
Error APIError `json:"error"`
|
|
}
|
|
|
|
// ── JWT Claims ────────────────────────────────────────────────────────────────
|
|
|
|
type Claims struct {
|
|
Sub string `json:"sub"`
|
|
TenantID string `json:"tenant_id"`
|
|
IsAdmin bool `json:"is_admin"`
|
|
}
|