readysite / website / models / file.go
3.5 KB
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)
}
← Back