Files
honeyDueAPI/internal/repositories/document_repo.go
Trey t 76579e8bf8 Add secure media access control with authenticated proxy endpoints
- Add MediaHandler with token-based proxy endpoints for serving media:
  - GET /api/media/document/:id
  - GET /api/media/document-image/:id
  - GET /api/media/completion-image/:id
- Add MediaURL fields to response DTOs for documents and task completions
- Add FindImageByID and FindCompletionImageByID repository methods
- Preload Completions.Images in all task queries for proper media URLs
- Remove public /uploads static file serving for security
- Verify residence access before serving any media files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 19:47:39 -06:00

156 lines
4.8 KiB
Go

package repositories
import (
"time"
"gorm.io/gorm"
"github.com/treytartt/casera-api/internal/models"
)
// DocumentRepository handles database operations for documents
type DocumentRepository struct {
db *gorm.DB
}
// NewDocumentRepository creates a new document repository
func NewDocumentRepository(db *gorm.DB) *DocumentRepository {
return &DocumentRepository{db: db}
}
// FindByID finds a document by ID with preloaded relations
func (r *DocumentRepository) FindByID(id uint) (*models.Document, error) {
var document models.Document
err := r.db.Preload("CreatedBy").
Preload("Task").
Preload("Images").
Where("id = ? AND is_active = ?", id, true).
First(&document).Error
if err != nil {
return nil, err
}
return &document, nil
}
// FindByResidence finds all documents for a residence
func (r *DocumentRepository) FindByResidence(residenceID uint) ([]models.Document, error) {
var documents []models.Document
err := r.db.Preload("CreatedBy").
Preload("Images").
Where("residence_id = ? AND is_active = ?", residenceID, true).
Order("created_at DESC").
Find(&documents).Error
return documents, err
}
// FindByUser finds all documents accessible to a user
func (r *DocumentRepository) FindByUser(residenceIDs []uint) ([]models.Document, error) {
var documents []models.Document
err := r.db.Preload("CreatedBy").
Preload("Residence").
Preload("Images").
Where("residence_id IN ? AND is_active = ?", residenceIDs, true).
Order("created_at DESC").
Find(&documents).Error
return documents, err
}
// FindWarranties finds all warranty documents
func (r *DocumentRepository) FindWarranties(residenceIDs []uint) ([]models.Document, error) {
var documents []models.Document
err := r.db.Preload("CreatedBy").
Preload("Residence").
Preload("Images").
Where("residence_id IN ? AND is_active = ? AND document_type = ?",
residenceIDs, true, models.DocumentTypeWarranty).
Order("expiry_date ASC NULLS LAST").
Find(&documents).Error
return documents, err
}
// FindExpiringWarranties finds warranties expiring within the specified days
func (r *DocumentRepository) FindExpiringWarranties(residenceIDs []uint, days int) ([]models.Document, error) {
threshold := time.Now().UTC().AddDate(0, 0, days)
now := time.Now().UTC()
var documents []models.Document
err := r.db.Preload("CreatedBy").
Preload("Residence").
Preload("Images").
Where("residence_id IN ? AND is_active = ? AND document_type = ? AND expiry_date > ? AND expiry_date <= ?",
residenceIDs, true, models.DocumentTypeWarranty, now, threshold).
Order("expiry_date ASC").
Find(&documents).Error
return documents, err
}
// Create creates a new document
func (r *DocumentRepository) Create(document *models.Document) error {
return r.db.Create(document).Error
}
// Update updates a document
func (r *DocumentRepository) Update(document *models.Document) error {
return r.db.Save(document).Error
}
// Delete soft-deletes a document
func (r *DocumentRepository) Delete(id uint) error {
return r.db.Model(&models.Document{}).
Where("id = ?", id).
Update("is_active", false).Error
}
// Activate activates a document
func (r *DocumentRepository) Activate(id uint) error {
return r.db.Model(&models.Document{}).
Where("id = ?", id).
Update("is_active", true).Error
}
// Deactivate deactivates a document
func (r *DocumentRepository) Deactivate(id uint) error {
return r.db.Model(&models.Document{}).
Where("id = ?", id).
Update("is_active", false).Error
}
// CountByResidence counts documents in a residence
func (r *DocumentRepository) CountByResidence(residenceID uint) (int64, error) {
var count int64
err := r.db.Model(&models.Document{}).
Where("residence_id = ? AND is_active = ?", residenceID, true).
Count(&count).Error
return count, err
}
// FindByIDIncludingInactive finds a document by ID including inactive ones
func (r *DocumentRepository) FindByIDIncludingInactive(id uint, document *models.Document) error {
return r.db.Preload("CreatedBy").Preload("Images").First(document, id).Error
}
// CreateDocumentImage creates a new document image
func (r *DocumentRepository) CreateDocumentImage(image *models.DocumentImage) error {
return r.db.Create(image).Error
}
// DeleteDocumentImage deletes a document image
func (r *DocumentRepository) DeleteDocumentImage(id uint) error {
return r.db.Delete(&models.DocumentImage{}, id).Error
}
// DeleteDocumentImages deletes all images for a document
func (r *DocumentRepository) DeleteDocumentImages(documentID uint) error {
return r.db.Where("document_id = ?", documentID).Delete(&models.DocumentImage{}).Error
}
// FindImageByID finds a document image by ID
func (r *DocumentRepository) FindImageByID(id uint) (*models.DocumentImage, error) {
var image models.DocumentImage
err := r.db.First(&image, id).Error
if err != nil {
return nil, err
}
return &image, nil
}