Add task templates API and admin management
- Add TaskTemplate model with category and frequency support - Add task template repository with CRUD and search operations - Add task template service layer - Add public API endpoints for templates (no auth required): - GET /api/tasks/templates/ - list all templates - GET /api/tasks/templates/grouped/ - templates grouped by category - GET /api/tasks/templates/search/?q= - search templates - GET /api/tasks/templates/by-category/:id/ - templates by category - GET /api/tasks/templates/:id/ - single template - Add admin panel for task template management (CRUD) - Add admin API endpoints for templates - Add seed file with predefined task templates - Add i18n translations for template errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
134
internal/dto/responses/task_template.go
Normal file
134
internal/dto/responses/task_template.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/treytartt/casera-api/internal/models"
|
||||
)
|
||||
|
||||
// TaskTemplateResponse represents a task template in the API response
|
||||
type TaskTemplateResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
Category *TaskCategoryResponse `json:"category,omitempty"`
|
||||
FrequencyID *uint `json:"frequency_id"`
|
||||
Frequency *TaskFrequencyResponse `json:"frequency,omitempty"`
|
||||
IconIOS string `json:"icon_ios"`
|
||||
IconAndroid string `json:"icon_android"`
|
||||
Tags []string `json:"tags"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TaskTemplateCategoryGroup represents templates grouped by category
|
||||
type TaskTemplateCategoryGroup struct {
|
||||
CategoryName string `json:"category_name"`
|
||||
CategoryID *uint `json:"category_id"`
|
||||
Templates []TaskTemplateResponse `json:"templates"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// TaskTemplatesGroupedResponse represents all templates grouped by category
|
||||
type TaskTemplatesGroupedResponse struct {
|
||||
Categories []TaskTemplateCategoryGroup `json:"categories"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
// NewTaskTemplateResponse creates a TaskTemplateResponse from a model
|
||||
func NewTaskTemplateResponse(t *models.TaskTemplate) TaskTemplateResponse {
|
||||
resp := TaskTemplateResponse{
|
||||
ID: t.ID,
|
||||
Title: t.Title,
|
||||
Description: t.Description,
|
||||
CategoryID: t.CategoryID,
|
||||
FrequencyID: t.FrequencyID,
|
||||
IconIOS: t.IconIOS,
|
||||
IconAndroid: t.IconAndroid,
|
||||
Tags: parseTags(t.Tags),
|
||||
DisplayOrder: t.DisplayOrder,
|
||||
IsActive: t.IsActive,
|
||||
CreatedAt: t.CreatedAt,
|
||||
UpdatedAt: t.UpdatedAt,
|
||||
}
|
||||
|
||||
if t.Category != nil {
|
||||
resp.Category = NewTaskCategoryResponse(t.Category)
|
||||
}
|
||||
if t.Frequency != nil {
|
||||
resp.Frequency = NewTaskFrequencyResponse(t.Frequency)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// NewTaskTemplateListResponse creates a list of task template responses
|
||||
func NewTaskTemplateListResponse(templates []models.TaskTemplate) []TaskTemplateResponse {
|
||||
results := make([]TaskTemplateResponse, len(templates))
|
||||
for i, t := range templates {
|
||||
results[i] = NewTaskTemplateResponse(&t)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// NewTaskTemplatesGroupedResponse creates a grouped response from templates
|
||||
func NewTaskTemplatesGroupedResponse(templates []models.TaskTemplate) TaskTemplatesGroupedResponse {
|
||||
// Group by category
|
||||
categoryMap := make(map[string]*TaskTemplateCategoryGroup)
|
||||
categoryOrder := []string{} // To maintain order
|
||||
|
||||
for _, t := range templates {
|
||||
categoryName := "Uncategorized"
|
||||
var categoryID *uint
|
||||
if t.Category != nil {
|
||||
categoryName = t.Category.Name
|
||||
categoryID = &t.Category.ID
|
||||
}
|
||||
|
||||
if _, exists := categoryMap[categoryName]; !exists {
|
||||
categoryMap[categoryName] = &TaskTemplateCategoryGroup{
|
||||
CategoryName: categoryName,
|
||||
CategoryID: categoryID,
|
||||
Templates: []TaskTemplateResponse{},
|
||||
}
|
||||
categoryOrder = append(categoryOrder, categoryName)
|
||||
}
|
||||
|
||||
categoryMap[categoryName].Templates = append(categoryMap[categoryName].Templates, NewTaskTemplateResponse(&t))
|
||||
}
|
||||
|
||||
// Build ordered result
|
||||
categories := make([]TaskTemplateCategoryGroup, len(categoryOrder))
|
||||
totalCount := 0
|
||||
for i, name := range categoryOrder {
|
||||
group := categoryMap[name]
|
||||
group.Count = len(group.Templates)
|
||||
totalCount += group.Count
|
||||
categories[i] = *group
|
||||
}
|
||||
|
||||
return TaskTemplatesGroupedResponse{
|
||||
Categories: categories,
|
||||
TotalCount: totalCount,
|
||||
}
|
||||
}
|
||||
|
||||
// parseTags splits a comma-separated tags string into a slice
|
||||
func parseTags(tags string) []string {
|
||||
if tags == "" {
|
||||
return []string{}
|
||||
}
|
||||
parts := strings.Split(tags, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
trimmed := strings.TrimSpace(p)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user