Initial commit: MyCrib API in Go
Complete rewrite of Django REST API to Go with: - Gin web framework for HTTP routing - GORM for database operations - GoAdmin for admin panel - Gorush integration for push notifications - Redis for caching and job queues Features implemented: - User authentication (login, register, logout, password reset) - Residence management (CRUD, sharing, share codes) - Task management (CRUD, kanban board, completions) - Contractor management (CRUD, specialties) - Document management (CRUD, warranties) - Notifications (preferences, push notifications) - Subscription management (tiers, limits) Infrastructure: - Docker Compose for local development - Database migrations and seed data - Admin panel for data management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
347
internal/repositories/task_repo.go
Normal file
347
internal/repositories/task_repo.go
Normal file
@@ -0,0 +1,347 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/treytartt/mycrib-api/internal/models"
|
||||
)
|
||||
|
||||
// TaskRepository handles database operations for tasks
|
||||
type TaskRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTaskRepository creates a new task repository
|
||||
func NewTaskRepository(db *gorm.DB) *TaskRepository {
|
||||
return &TaskRepository{db: db}
|
||||
}
|
||||
|
||||
// === Task CRUD ===
|
||||
|
||||
// FindByID finds a task by ID with preloaded relations
|
||||
func (r *TaskRepository) FindByID(id uint) (*models.Task, error) {
|
||||
var task models.Task
|
||||
err := r.db.Preload("Residence").
|
||||
Preload("CreatedBy").
|
||||
Preload("AssignedTo").
|
||||
Preload("Category").
|
||||
Preload("Priority").
|
||||
Preload("Status").
|
||||
Preload("Frequency").
|
||||
Preload("Completions").
|
||||
Preload("Completions.CompletedBy").
|
||||
First(&task, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
// FindByResidence finds all tasks for a residence
|
||||
func (r *TaskRepository) FindByResidence(residenceID uint) ([]models.Task, error) {
|
||||
var tasks []models.Task
|
||||
err := r.db.Preload("CreatedBy").
|
||||
Preload("AssignedTo").
|
||||
Preload("Category").
|
||||
Preload("Priority").
|
||||
Preload("Status").
|
||||
Preload("Frequency").
|
||||
Preload("Completions").
|
||||
Where("residence_id = ?", residenceID).
|
||||
Order("due_date ASC NULLS LAST, created_at DESC").
|
||||
Find(&tasks).Error
|
||||
return tasks, err
|
||||
}
|
||||
|
||||
// FindByUser finds all tasks accessible to a user (across all their residences)
|
||||
func (r *TaskRepository) FindByUser(userID uint, residenceIDs []uint) ([]models.Task, error) {
|
||||
var tasks []models.Task
|
||||
err := r.db.Preload("Residence").
|
||||
Preload("CreatedBy").
|
||||
Preload("AssignedTo").
|
||||
Preload("Category").
|
||||
Preload("Priority").
|
||||
Preload("Status").
|
||||
Preload("Frequency").
|
||||
Preload("Completions").
|
||||
Where("residence_id IN ?", residenceIDs).
|
||||
Order("due_date ASC NULLS LAST, created_at DESC").
|
||||
Find(&tasks).Error
|
||||
return tasks, err
|
||||
}
|
||||
|
||||
// Create creates a new task
|
||||
func (r *TaskRepository) Create(task *models.Task) error {
|
||||
return r.db.Create(task).Error
|
||||
}
|
||||
|
||||
// Update updates a task
|
||||
func (r *TaskRepository) Update(task *models.Task) error {
|
||||
return r.db.Save(task).Error
|
||||
}
|
||||
|
||||
// Delete hard-deletes a task
|
||||
func (r *TaskRepository) Delete(id uint) error {
|
||||
return r.db.Delete(&models.Task{}, id).Error
|
||||
}
|
||||
|
||||
// === Task State Operations ===
|
||||
|
||||
// MarkInProgress marks a task as in progress
|
||||
func (r *TaskRepository) MarkInProgress(id uint, statusID uint) error {
|
||||
return r.db.Model(&models.Task{}).
|
||||
Where("id = ?", id).
|
||||
Update("status_id", statusID).Error
|
||||
}
|
||||
|
||||
// Cancel cancels a task
|
||||
func (r *TaskRepository) Cancel(id uint) error {
|
||||
return r.db.Model(&models.Task{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_cancelled", true).Error
|
||||
}
|
||||
|
||||
// Uncancel uncancels a task
|
||||
func (r *TaskRepository) Uncancel(id uint) error {
|
||||
return r.db.Model(&models.Task{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_cancelled", false).Error
|
||||
}
|
||||
|
||||
// Archive archives a task
|
||||
func (r *TaskRepository) Archive(id uint) error {
|
||||
return r.db.Model(&models.Task{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_archived", true).Error
|
||||
}
|
||||
|
||||
// Unarchive unarchives a task
|
||||
func (r *TaskRepository) Unarchive(id uint) error {
|
||||
return r.db.Model(&models.Task{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_archived", false).Error
|
||||
}
|
||||
|
||||
// === Kanban Board ===
|
||||
|
||||
// GetKanbanData retrieves tasks organized for kanban display
|
||||
func (r *TaskRepository) GetKanbanData(residenceID uint, daysThreshold int) (*models.KanbanBoard, error) {
|
||||
var tasks []models.Task
|
||||
err := r.db.Preload("CreatedBy").
|
||||
Preload("AssignedTo").
|
||||
Preload("Category").
|
||||
Preload("Priority").
|
||||
Preload("Status").
|
||||
Preload("Frequency").
|
||||
Preload("Completions").
|
||||
Preload("Completions.CompletedBy").
|
||||
Where("residence_id = ? AND is_archived = ?", residenceID, false).
|
||||
Order("due_date ASC NULLS LAST, priority_id DESC, created_at DESC").
|
||||
Find(&tasks).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Organize into columns
|
||||
now := time.Now().UTC()
|
||||
threshold := now.AddDate(0, 0, daysThreshold)
|
||||
|
||||
overdue := make([]models.Task, 0)
|
||||
dueSoon := make([]models.Task, 0)
|
||||
upcoming := make([]models.Task, 0)
|
||||
inProgress := make([]models.Task, 0)
|
||||
completed := make([]models.Task, 0)
|
||||
cancelled := make([]models.Task, 0)
|
||||
|
||||
for _, task := range tasks {
|
||||
if task.IsCancelled {
|
||||
cancelled = append(cancelled, task)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if completed (has completions)
|
||||
if len(task.Completions) > 0 {
|
||||
completed = append(completed, task)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check status for in-progress (status_id = 2 typically)
|
||||
if task.Status != nil && task.Status.Name == "In Progress" {
|
||||
inProgress = append(inProgress, task)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check due date
|
||||
if task.DueDate != nil {
|
||||
if task.DueDate.Before(now) {
|
||||
overdue = append(overdue, task)
|
||||
} else if task.DueDate.Before(threshold) {
|
||||
dueSoon = append(dueSoon, task)
|
||||
} else {
|
||||
upcoming = append(upcoming, task)
|
||||
}
|
||||
} else {
|
||||
upcoming = append(upcoming, task)
|
||||
}
|
||||
}
|
||||
|
||||
columns := []models.KanbanColumn{
|
||||
{
|
||||
Name: "overdue_tasks",
|
||||
DisplayName: "Overdue",
|
||||
ButtonTypes: []string{"edit", "cancel", "mark_in_progress"},
|
||||
Icons: map[string]string{"ios": "exclamationmark.triangle", "android": "Warning"},
|
||||
Color: "#FF3B30",
|
||||
Tasks: overdue,
|
||||
Count: len(overdue),
|
||||
},
|
||||
{
|
||||
Name: "due_soon_tasks",
|
||||
DisplayName: "Due Soon",
|
||||
ButtonTypes: []string{"edit", "complete", "mark_in_progress"},
|
||||
Icons: map[string]string{"ios": "clock", "android": "Schedule"},
|
||||
Color: "#FF9500",
|
||||
Tasks: dueSoon,
|
||||
Count: len(dueSoon),
|
||||
},
|
||||
{
|
||||
Name: "upcoming_tasks",
|
||||
DisplayName: "Upcoming",
|
||||
ButtonTypes: []string{"edit", "cancel"},
|
||||
Icons: map[string]string{"ios": "calendar", "android": "Event"},
|
||||
Color: "#007AFF",
|
||||
Tasks: upcoming,
|
||||
Count: len(upcoming),
|
||||
},
|
||||
{
|
||||
Name: "in_progress_tasks",
|
||||
DisplayName: "In Progress",
|
||||
ButtonTypes: []string{"edit", "complete"},
|
||||
Icons: map[string]string{"ios": "hammer", "android": "Build"},
|
||||
Color: "#5856D6",
|
||||
Tasks: inProgress,
|
||||
Count: len(inProgress),
|
||||
},
|
||||
{
|
||||
Name: "completed_tasks",
|
||||
DisplayName: "Completed",
|
||||
ButtonTypes: []string{"view"},
|
||||
Icons: map[string]string{"ios": "checkmark.circle", "android": "CheckCircle"},
|
||||
Color: "#34C759",
|
||||
Tasks: completed,
|
||||
Count: len(completed),
|
||||
},
|
||||
{
|
||||
Name: "cancelled_tasks",
|
||||
DisplayName: "Cancelled",
|
||||
ButtonTypes: []string{"uncancel", "delete"},
|
||||
Icons: map[string]string{"ios": "xmark.circle", "android": "Cancel"},
|
||||
Color: "#8E8E93",
|
||||
Tasks: cancelled,
|
||||
Count: len(cancelled),
|
||||
},
|
||||
}
|
||||
|
||||
return &models.KanbanBoard{
|
||||
Columns: columns,
|
||||
DaysThreshold: daysThreshold,
|
||||
ResidenceID: string(rune(residenceID)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// === Lookup Operations ===
|
||||
|
||||
// GetAllCategories returns all task categories
|
||||
func (r *TaskRepository) GetAllCategories() ([]models.TaskCategory, error) {
|
||||
var categories []models.TaskCategory
|
||||
err := r.db.Order("display_order, name").Find(&categories).Error
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// GetAllPriorities returns all task priorities
|
||||
func (r *TaskRepository) GetAllPriorities() ([]models.TaskPriority, error) {
|
||||
var priorities []models.TaskPriority
|
||||
err := r.db.Order("level").Find(&priorities).Error
|
||||
return priorities, err
|
||||
}
|
||||
|
||||
// GetAllStatuses returns all task statuses
|
||||
func (r *TaskRepository) GetAllStatuses() ([]models.TaskStatus, error) {
|
||||
var statuses []models.TaskStatus
|
||||
err := r.db.Order("display_order").Find(&statuses).Error
|
||||
return statuses, err
|
||||
}
|
||||
|
||||
// GetAllFrequencies returns all task frequencies
|
||||
func (r *TaskRepository) GetAllFrequencies() ([]models.TaskFrequency, error) {
|
||||
var frequencies []models.TaskFrequency
|
||||
err := r.db.Order("display_order").Find(&frequencies).Error
|
||||
return frequencies, err
|
||||
}
|
||||
|
||||
// FindStatusByName finds a status by name
|
||||
func (r *TaskRepository) FindStatusByName(name string) (*models.TaskStatus, error) {
|
||||
var status models.TaskStatus
|
||||
err := r.db.Where("name = ?", name).First(&status).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
// CountByResidence counts tasks in a residence
|
||||
func (r *TaskRepository) CountByResidence(residenceID uint) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&models.Task{}).
|
||||
Where("residence_id = ? AND is_cancelled = ? AND is_archived = ?", residenceID, false, false).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// === Task Completion Operations ===
|
||||
|
||||
// CreateCompletion creates a new task completion
|
||||
func (r *TaskRepository) CreateCompletion(completion *models.TaskCompletion) error {
|
||||
return r.db.Create(completion).Error
|
||||
}
|
||||
|
||||
// FindCompletionByID finds a completion by ID
|
||||
func (r *TaskRepository) FindCompletionByID(id uint) (*models.TaskCompletion, error) {
|
||||
var completion models.TaskCompletion
|
||||
err := r.db.Preload("Task").
|
||||
Preload("CompletedBy").
|
||||
First(&completion, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &completion, nil
|
||||
}
|
||||
|
||||
// FindCompletionsByTask finds all completions for a task
|
||||
func (r *TaskRepository) FindCompletionsByTask(taskID uint) ([]models.TaskCompletion, error) {
|
||||
var completions []models.TaskCompletion
|
||||
err := r.db.Preload("CompletedBy").
|
||||
Where("task_id = ?", taskID).
|
||||
Order("completed_at DESC").
|
||||
Find(&completions).Error
|
||||
return completions, err
|
||||
}
|
||||
|
||||
// FindCompletionsByUser finds all completions by a user
|
||||
func (r *TaskRepository) FindCompletionsByUser(userID uint, residenceIDs []uint) ([]models.TaskCompletion, error) {
|
||||
var completions []models.TaskCompletion
|
||||
err := r.db.Preload("Task").
|
||||
Preload("CompletedBy").
|
||||
Joins("JOIN task_task ON task_task.id = task_taskcompletion.task_id").
|
||||
Where("task_task.residence_id IN ?", residenceIDs).
|
||||
Order("completed_at DESC").
|
||||
Find(&completions).Error
|
||||
return completions, err
|
||||
}
|
||||
|
||||
// DeleteCompletion deletes a task completion
|
||||
func (r *TaskRepository) DeleteCompletion(id uint) error {
|
||||
return r.db.Delete(&models.TaskCompletion{}, id).Error
|
||||
}
|
||||
Reference in New Issue
Block a user