feat: V2 microservices stack — backend services, gateway, JWT auth
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>
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user