Files
apocapoc-api/internal/infrastructure/http/dto.go
T
david aa8f7af55d feat: implement offline sync endpoints with Last-Write-Wins strategy
Add comprehensive offline synchronization support for habits and entries:

## Infrastructure (Phase 1)
- Add UpdatedAt and DeletedAt timestamps to Habit and HabitEntry entities
- Implement soft delete with Delete(), Touch(), and IsDeleted() methods
- Create SQL migration with optimized composite indexes for sync queries
- Add GetChangesSince() and SoftDelete() to both repositories
- Update all Find* methods to exclude soft-deleted records
- 13 comprehensive TDD tests for sync repository methods

## HTTP Endpoints (Phase 2)
- GET /api/v1/sync/changes: retrieve all changes since timestamp
- POST /api/v1/sync/batch: apply client changes with conflict resolution
- Implement Last-Write-Wins strategy using UpdatedAt timestamps
- Add authentication and rate limiting (100 req/min)
- Validate user ownership for all sync operations
- 9 tests for sync handlers (3 queries + 6 commands)

## Technical Details
- Composite indexes: (user_id, updated_at) for optimal query performance
- No pagination: atomic sync operations for data consistency
- Upsert behavior: create resources if not found on server
- DTOs with full entity state including timestamps
- Swagger documentation updated for new endpoints

All 220+ tests passing ✓
2025-12-12 00:11:46 +01:00

156 lines
5.6 KiB
Go

package http
import (
"time"
"apocapoc-api/internal/domain/value_objects"
"apocapoc-api/internal/shared/pagination"
)
type CreateHabitRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Type value_objects.HabitType `json:"type"`
Frequency value_objects.Frequency `json:"frequency"`
SpecificDays []int `json:"specific_days,omitempty"`
SpecificDates []int `json:"specific_dates,omitempty"`
CarryOver bool `json:"carry_over"`
IsNegative bool `json:"is_negative"`
TargetValue *float64 `json:"target_value,omitempty"`
}
type UpdateHabitRequest struct {
Name string `json:"name"`
Description string `json:"description"`
SpecificDays []int `json:"specific_days,omitempty"`
SpecificDates []int `json:"specific_dates,omitempty"`
CarryOver bool `json:"carry_over"`
TargetValue *float64 `json:"target_value,omitempty"`
}
type HabitResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
Type value_objects.HabitType `json:"type"`
Frequency value_objects.Frequency `json:"frequency"`
SpecificDays []int `json:"specific_days,omitempty"`
SpecificDates []int `json:"specific_dates,omitempty"`
CarryOver bool `json:"carry_over"`
IsNegative bool `json:"is_negative"`
TargetValue *float64 `json:"target_value,omitempty"`
CreatedAt time.Time `json:"created_at"`
ArchivedAt *time.Time `json:"archived_at,omitempty"`
}
type MarkHabitRequest struct {
ScheduledDate string `json:"scheduled_date"`
Value *float64 `json:"value,omitempty"`
}
type TodaysHabitEntryResponse struct {
ID string `json:"id"`
Value *float64 `json:"value,omitempty"`
CompletedAt time.Time `json:"completed_at"`
}
type TodaysHabitResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Type value_objects.HabitType `json:"type"`
TargetValue *float64 `json:"target_value,omitempty"`
IsNegative bool `json:"is_negative"`
ScheduledDate time.Time `json:"scheduled_date"`
IsCarriedOver bool `json:"is_carried_over"`
Entry *TodaysHabitEntryResponse `json:"entry,omitempty"`
}
type UserHabitResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Type value_objects.HabitType `json:"type"`
Frequency value_objects.Frequency `json:"frequency"`
SpecificDays []int `json:"specific_days,omitempty"`
TargetValue *float64 `json:"target_value,omitempty"`
CarryOver bool `json:"carry_over"`
IsNegative bool `json:"is_negative"`
}
type GetUserHabitsResponse struct {
Data []UserHabitResponse `json:"data"`
Pagination *pagination.Response `json:"pagination,omitempty"`
}
type HabitEntryResponse struct {
ID string `json:"id"`
HabitID string `json:"habit_id"`
ScheduledDate time.Time `json:"scheduled_date"`
CompletedAt time.Time `json:"completed_at"`
Value *float64 `json:"value,omitempty"`
}
type HabitEntriesResponse struct {
Entries []HabitEntryResponse `json:"entries"`
Total int `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
}
type ErrorResponse struct {
Error string `json:"error"`
}
type ValidationErrorResponse struct {
Error string `json:"error"`
Field string `json:"field"`
}
type SyncHabitDTO struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
Type value_objects.HabitType `json:"type"`
Frequency value_objects.Frequency `json:"frequency"`
SpecificDays []int `json:"specific_days,omitempty"`
SpecificDates []int `json:"specific_dates,omitempty"`
CarryOver bool `json:"carry_over"`
IsNegative bool `json:"is_negative"`
TargetValue *float64 `json:"target_value,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ArchivedAt *time.Time `json:"archived_at,omitempty"`
}
type SyncHabitEntryDTO struct {
ID string `json:"id"`
HabitID string `json:"habit_id"`
ScheduledDate time.Time `json:"scheduled_date"`
CompletedAt time.Time `json:"completed_at"`
Value *float64 `json:"value,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
}
type HabitChangesDTO struct {
Created []SyncHabitDTO `json:"created"`
Updated []SyncHabitDTO `json:"updated"`
Deleted []string `json:"deleted"`
}
type EntryChangesDTO struct {
Created []SyncHabitEntryDTO `json:"created"`
Updated []SyncHabitEntryDTO `json:"updated"`
Deleted []string `json:"deleted"`
}
type SyncChangesResponse struct {
Habits HabitChangesDTO `json:"habits"`
Entries EntryChangesDTO `json:"entries"`
}
type SyncBatchRequest struct {
Habits HabitChangesDTO `json:"habits"`
Entries EntryChangesDTO `json:"entries"`
}