Add PDF reports, file uploads, admin auth, and comprehensive tests
Features: - PDF service for generating task reports with ReportLab-style formatting - Storage service for file uploads (local and S3-compatible) - Admin authentication middleware with JWT support - Admin user model and repository Infrastructure: - Updated Docker configuration for admin panel builds - Email service enhancements for task notifications - Updated router with admin and file upload routes - Environment configuration updates Tests: - Unit tests for handlers (auth, residence, task) - Unit tests for models (user, residence, task) - Unit tests for repositories (user, residence, task) - Unit tests for services (residence, task) - Integration test setup - Test utilities for mocking database and services Database: - Admin user seed data - Updated test data seeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,57 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// FlexibleDate handles both "2025-11-27" and "2025-11-27T00:00:00Z" formats
|
||||
type FlexibleDate struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (fd *FlexibleDate) UnmarshalJSON(data []byte) error {
|
||||
// Remove quotes
|
||||
s := strings.Trim(string(data), "\"")
|
||||
if s == "" || s == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try RFC3339 first (full datetime)
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err == nil {
|
||||
fd.Time = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try date-only format
|
||||
t, err = time.Parse("2006-01-02", s)
|
||||
if err == nil {
|
||||
fd.Time = t
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (fd FlexibleDate) MarshalJSON() ([]byte, error) {
|
||||
if fd.Time.IsZero() {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
return json.Marshal(fd.Time.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// ToTimePtr returns a pointer to the underlying time, or nil if zero
|
||||
func (fd *FlexibleDate) ToTimePtr() *time.Time {
|
||||
if fd == nil || fd.Time.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &fd.Time
|
||||
}
|
||||
|
||||
// CreateTaskRequest represents the request to create a task
|
||||
type CreateTaskRequest struct {
|
||||
ResidenceID uint `json:"residence_id" binding:"required"`
|
||||
@@ -16,7 +62,7 @@ type CreateTaskRequest struct {
|
||||
StatusID *uint `json:"status_id"`
|
||||
FrequencyID *uint `json:"frequency_id"`
|
||||
AssignedToID *uint `json:"assigned_to_id"`
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
DueDate *FlexibleDate `json:"due_date"`
|
||||
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
|
||||
ContractorID *uint `json:"contractor_id"`
|
||||
}
|
||||
@@ -30,7 +76,7 @@ type UpdateTaskRequest struct {
|
||||
StatusID *uint `json:"status_id"`
|
||||
FrequencyID *uint `json:"frequency_id"`
|
||||
AssignedToID *uint `json:"assigned_to_id"`
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
DueDate *FlexibleDate `json:"due_date"`
|
||||
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
|
||||
ActualCost *decimal.Decimal `json:"actual_cost"`
|
||||
ContractorID *uint `json:"contractor_id"`
|
||||
|
||||
Reference in New Issue
Block a user