file.go
package models
import (
"fmt"
"github.com/readysite/readysite/pkg/database"
)
// File represents an uploaded file stored in the database.
type File struct {
database.Model
Name string // Original filename
MimeType string // MIME type (e.g., "image/png", "application/pdf")
Size int64 // File size in bytes
Data []byte // File content
UserID string // User who uploaded the file
Published bool // Whether the file is publicly accessible at its path
Path string // Public URL path (e.g., "images/logo.png")
}
// User returns the user who uploaded this file.
func (f *File) User() (*User, error) {
if f.UserID == "" {
return nil, nil
}
return Users.Get(f.UserID)
}
// PublicURL returns the public URL for this file if it's published with a path.
func (f *File) PublicURL() string {
if !f.Published || f.Path == "" {
return ""
}
return "/_file/" + f.Path
}
// SizeFormatted returns the file size in a human-readable format.
func (f *File) SizeFormatted() string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
switch {
case f.Size >= GB:
return fmt.Sprintf("%.1f GB", float64(f.Size)/GB)
case f.Size >= MB:
return fmt.Sprintf("%.1f MB", float64(f.Size)/MB)
case f.Size >= KB:
return fmt.Sprintf("%.1f KB", float64(f.Size)/KB)
default:
return fmt.Sprintf("%d bytes", f.Size)
}
}
// IsImage returns true if the file is an image.
func (f *File) IsImage() bool {
switch f.MimeType {
case "image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml":
return true
}
return false
}
// IsPDF returns true if the file is a PDF.
func (f *File) IsPDF() bool {
return f.MimeType == "application/pdf"
}
// IsText returns true if the file is text-based.
func (f *File) IsText() bool {
switch f.MimeType {
case "text/plain", "text/html", "text/css", "text/javascript",
"application/json", "application/xml", "text/markdown":
return true
}
return false
}
// Extension returns the file extension based on MIME type.
func (f *File) Extension() string {
switch f.MimeType {
case "image/png":
return "png"
case "image/jpeg":
return "jpg"
case "image/gif":
return "gif"
case "image/webp":
return "webp"
case "image/svg+xml":
return "svg"
case "application/pdf":
return "pdf"
case "text/plain":
return "txt"
case "text/html":
return "html"
case "text/css":
return "css"
case "text/javascript", "application/javascript":
return "js"
case "application/json":
return "json"
case "application/xml", "text/xml":
return "xml"
case "text/markdown":
return "md"
default:
return ""
}
}
// --- MessageFile ---
// MessageFile links a file to a message for AI context.
type MessageFile struct {
database.Model
MessageID string // Message this file is attached to
FileID string // The file being attached
}
// Message returns the message this file is attached to.
func (mf *MessageFile) Message() (*Message, error) {
return Messages.Get(mf.MessageID)
}
// File returns the attached file.
func (mf *MessageFile) File() (*File, error) {
return Files.Get(mf.FileID)
}
// FileContentCache stores extracted text content from files for AI context.
// This avoids re-extracting text content on every AI request.
type FileContentCache struct {
database.Model
FileID string // Reference to File.ID
ContentHash string // SHA-256 hash of file content
TextContent string // Extracted text for AI consumption
TokenCount int // Estimated token count
}
// File returns the associated file.
func (c *FileContentCache) File() (*File, error) {
return Files.Get(c.FileID)
}