package models import "time" // UploadCategory enumerates the kinds of objects that can be uploaded via the // presigned-URL flow. Each category has its own size cap and mime-type // allow-list enforced at the service layer. type UploadCategory string const ( UploadCategoryCompletion UploadCategory = "completion" UploadCategoryDocumentImage UploadCategory = "document_image" UploadCategoryDocumentFile UploadCategory = "document_file" ) // PendingUpload is a short-lived upload session created when the client asks // for a presigned POST policy. The row tracks the intent so the server can // validate quota / rate-limit / size up front, then attach the resulting B2 // object to a task_completion_image or document_image once the upload lands. // // Lifecycle: // // created → upload to B2 → attach via /api/task-completions/ or /documents/ // ↑ │ // └─ if not claimed before expires_at, the cleanup worker (see // internal/worker/jobs) deletes the B2 object and the row. type PendingUpload struct { ID uint `gorm:"primaryKey" json:"id"` UserID uint `gorm:"column:user_id;not null;index:idx_pending_uploads_user_created,priority:1" json:"user_id"` Category UploadCategory `gorm:"column:category;size:32;not null" json:"category"` B2Key string `gorm:"column:b2_key;size:255;uniqueIndex" json:"b2_key"` ContentType string `gorm:"column:content_type;size:127;not null" json:"content_type"` ExpectedBytes int64 `gorm:"column:expected_bytes;not null" json:"expected_bytes"` ActualBytes *int64 `gorm:"column:actual_bytes" json:"actual_bytes,omitempty"` ClaimedAt *time.Time `gorm:"column:claimed_at" json:"claimed_at,omitempty"` CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;index:idx_pending_uploads_user_created,priority:2,sort:desc" json:"created_at"` ExpiresAt time.Time `gorm:"column:expires_at;not null" json:"expires_at"` } // TableName matches the goose migration. func (PendingUpload) TableName() string { return "pending_uploads" } // IsClaimed reports whether the upload has been linked to a real entity. func (p *PendingUpload) IsClaimed() bool { return p.ClaimedAt != nil } // IsExpired reports whether the upload session has passed its TTL. func (p *PendingUpload) IsExpired(now time.Time) bool { return now.After(p.ExpiresAt) }