Files
honeyDueAPI/internal/dto/requests/task.go
T
Trey t b7f83293b8
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
refactor(uploads): drop legacy multipart code paths
The presigned-URL upload flow (POST /api/uploads/presign + direct B2 POST
+ upload_ids[] in entity creation) is now the only image upload path. The
legacy multipart routes and DTO fields used by older clients are removed:

Removed:
  - POST /api/uploads/image/        (legacy multipart upload → URL)
  - POST /api/uploads/document/     (legacy multipart upload → URL)
  - POST /api/uploads/completion/   (legacy multipart upload → URL)
  - Multipart branch in POST /api/task-completions/ (now JSON-only)
  - CreateTaskCompletionRequest.ImageURLs DTO field
  - UpdateTaskCompletionRequest.ImageURLs DTO field
  - CreateDocumentRequest.ImageURLs DTO field
  - Service-layer ImageURLs loops in task_service.CreateCompletion,
    task_service.UpdateCompletion, document_service.CreateDocument
  - Tests exercising the removed paths
  - Now-unused imports (strings/time/decimal) in task_handler.go

Kept:
  - DELETE /api/uploads/  (orphan-cleanup endpoint, still useful)
  - POST /api/uploads/presign/  (the new path)
  - POST /api/documents/:id/images/  (uses storage_service.Upload directly,
    same multipart pattern but separate code path; deferred for now)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:19:21 -07:00

131 lines
5.0 KiB
Go

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
}
// BulkCreateTasksRequest represents a batch create. Used by onboarding to
// insert 1-N selected tasks atomically in a single transaction so that a
// failure halfway through doesn't leave a partial task list behind.
//
// ResidenceID is validated once at the service layer; individual task
// entries must reference the same residence or be left empty (the service
// overrides each entry's ResidenceID with the top-level value).
type BulkCreateTasksRequest struct {
ResidenceID uint `json:"residence_id" validate:"required"`
Tasks []CreateTaskRequest `json:"tasks" validate:"required,min=1,max=50,dive"`
}
// CreateTaskRequest represents the request to create a task
type CreateTaskRequest struct {
ResidenceID uint `json:"residence_id" validate:"required"`
Title string `json:"title" validate:"required,min=1,max=200"`
Description string `json:"description" validate:"max=10000"`
CategoryID *uint `json:"category_id"`
PriorityID *uint `json:"priority_id"`
FrequencyID *uint `json:"frequency_id"`
CustomIntervalDays *int `json:"custom_interval_days" validate:"omitempty,min=1"` // For "Custom" frequency, user-specified days
InProgress bool `json:"in_progress"`
AssignedToID *uint `json:"assigned_to_id"`
DueDate *FlexibleDate `json:"due_date"`
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
ContractorID *uint `json:"contractor_id"`
// TemplateID links the created task to the TaskTemplate it was spawned from
// (e.g. onboarding suggestion or catalog pick). Optional — custom tasks
// leave this nil.
TemplateID *uint `json:"template_id"`
}
// UpdateTaskRequest represents the request to update a task
type UpdateTaskRequest struct {
Title *string `json:"title" validate:"omitempty,min=1,max=200"`
Description *string `json:"description" validate:"omitempty,max=10000"`
CategoryID *uint `json:"category_id"`
PriorityID *uint `json:"priority_id"`
FrequencyID *uint `json:"frequency_id"`
CustomIntervalDays *int `json:"custom_interval_days" validate:"omitempty,min=1"` // For "Custom" frequency, user-specified days
InProgress *bool `json:"in_progress"`
AssignedToID *uint `json:"assigned_to_id"`
DueDate *FlexibleDate `json:"due_date"`
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
ActualCost *decimal.Decimal `json:"actual_cost"`
ContractorID *uint `json:"contractor_id"`
}
// CreateTaskCompletionRequest represents the request to create a task completion.
//
// Image attachments arrive via the presigned-URL flow: the client uploads
// each image directly to B2 (see /api/uploads/presign) and passes the
// resulting pending_uploads.id values in UploadIDs. The service claims
// those rows and creates the linked task_completion_image rows.
type CreateTaskCompletionRequest struct {
TaskID uint `json:"task_id" validate:"required"`
CompletedAt *time.Time `json:"completed_at"` // Defaults to now
Notes string `json:"notes" validate:"max=10000"`
ActualCost *decimal.Decimal `json:"actual_cost"`
Rating *int `json:"rating" validate:"omitempty,min=1,max=5"` // 1-5 star rating
UploadIDs []uint `json:"upload_ids" validate:"omitempty,max=20"`
}
// UpdateTaskCompletionRequest represents the request to update a task completion
type UpdateTaskCompletionRequest struct {
Notes *string `json:"notes" validate:"omitempty,max=10000"`
ActualCost *decimal.Decimal `json:"actual_cost"`
Rating *int `json:"rating" validate:"omitempty,min=1,max=5"`
}
// CompletionImageInput represents an image to add to a completion
type CompletionImageInput struct {
ImageURL string `json:"image_url" validate:"required"`
Caption string `json:"caption"`
}