Add multi-image support for task completions and documents
- Add TaskCompletionImage and DocumentImage models with one-to-many relationships
- Update admin panel to display images for completions and documents
- Add image arrays to API request/response DTOs
- Update repositories with Preload("Images") for eager loading
- Fix seed SQL execution to use raw SQL instead of prepared statements
- Fix table names in seed file (admin_users, push_notifications_*)
- Add comprehensive seed test data with 34 completion images and 24 document images
- Add subscription limitations admin feature with toggle
- Update admin sidebar with limitations link
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ type CreateDocumentRequest struct {
|
||||
SerialNumber string `json:"serial_number" binding:"max=100"`
|
||||
ModelNumber string `json:"model_number" binding:"max=100"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
ImageURLs []string `json:"image_urls"` // Multiple image URLs
|
||||
}
|
||||
|
||||
// UpdateDocumentRequest represents the request to update a document
|
||||
|
||||
@@ -88,5 +88,12 @@ type CreateTaskCompletionRequest struct {
|
||||
CompletedAt *time.Time `json:"completed_at"` // Defaults to now
|
||||
Notes string `json:"notes"`
|
||||
ActualCost *decimal.Decimal `json:"actual_cost"`
|
||||
PhotoURL string `json:"photo_url"`
|
||||
Rating *int `json:"rating"` // 1-5 star rating
|
||||
ImageURLs []string `json:"image_urls"` // Multiple image URLs
|
||||
}
|
||||
|
||||
// CompletionImageInput represents an image to add to a completion
|
||||
type CompletionImageInput struct {
|
||||
ImageURL string `json:"image_url" binding:"required"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ type DocumentUserResponse struct {
|
||||
LastName string `json:"last_name"`
|
||||
}
|
||||
|
||||
// DocumentImageResponse represents an image in a document
|
||||
type DocumentImageResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
// DocumentResponse represents a document in the API response
|
||||
type DocumentResponse struct {
|
||||
ID uint `json:"id"`
|
||||
@@ -36,10 +43,11 @@ type DocumentResponse struct {
|
||||
Vendor string `json:"vendor"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
ModelNumber string `json:"model_number"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Images []DocumentImageResponse `json:"images"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Note: Pagination removed - list endpoints now return arrays directly
|
||||
@@ -81,6 +89,7 @@ func NewDocumentResponse(d *models.Document) DocumentResponse {
|
||||
ModelNumber: d.ModelNumber,
|
||||
TaskID: d.TaskID,
|
||||
IsActive: d.IsActive,
|
||||
Images: make([]DocumentImageResponse, 0),
|
||||
CreatedAt: d.CreatedAt,
|
||||
UpdatedAt: d.UpdatedAt,
|
||||
}
|
||||
@@ -89,6 +98,15 @@ func NewDocumentResponse(d *models.Document) DocumentResponse {
|
||||
resp.CreatedBy = NewDocumentUserResponse(&d.CreatedBy)
|
||||
}
|
||||
|
||||
// Convert images
|
||||
for _, img := range d.Images {
|
||||
resp.Images = append(resp.Images, DocumentImageResponse{
|
||||
ID: img.ID,
|
||||
ImageURL: img.ImageURL,
|
||||
Caption: img.Caption,
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
|
||||
@@ -54,16 +54,24 @@ type TaskUserResponse struct {
|
||||
LastName string `json:"last_name"`
|
||||
}
|
||||
|
||||
// TaskCompletionImageResponse represents a completion image
|
||||
type TaskCompletionImageResponse struct {
|
||||
ID uint `json:"id"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
// TaskCompletionResponse represents a task completion
|
||||
type TaskCompletionResponse struct {
|
||||
ID uint `json:"id"`
|
||||
TaskID uint `json:"task_id"`
|
||||
CompletedBy *TaskUserResponse `json:"completed_by,omitempty"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
Notes string `json:"notes"`
|
||||
ActualCost *decimal.Decimal `json:"actual_cost"`
|
||||
PhotoURL string `json:"photo_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID uint `json:"id"`
|
||||
TaskID uint `json:"task_id"`
|
||||
CompletedBy *TaskUserResponse `json:"completed_by,omitempty"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
Notes string `json:"notes"`
|
||||
ActualCost *decimal.Decimal `json:"actual_cost"`
|
||||
Rating *int `json:"rating"`
|
||||
Images []TaskCompletionImageResponse `json:"images"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TaskResponse represents a task in the API response
|
||||
@@ -198,12 +206,21 @@ func NewTaskCompletionResponse(c *models.TaskCompletion) TaskCompletionResponse
|
||||
CompletedAt: c.CompletedAt,
|
||||
Notes: c.Notes,
|
||||
ActualCost: c.ActualCost,
|
||||
PhotoURL: c.PhotoURL,
|
||||
Rating: c.Rating,
|
||||
Images: make([]TaskCompletionImageResponse, 0),
|
||||
CreatedAt: c.CreatedAt,
|
||||
}
|
||||
if c.CompletedBy.ID != 0 {
|
||||
resp.CompletedBy = NewTaskUserResponse(&c.CompletedBy)
|
||||
}
|
||||
// Convert images
|
||||
for _, img := range c.Images {
|
||||
resp.Images = append(resp.Images, TaskCompletionImageResponse{
|
||||
ID: img.ID,
|
||||
ImageURL: img.ImageURL,
|
||||
Caption: img.Caption,
|
||||
})
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user