Files
apocapoc-api/internal/application/queries/export_user_data_handler.go
T
david 788b6cf430 Add data export functionality with gzip compression
- Add FindByUserID method to HabitEntryRepository (JOIN with habits)
- Implement ExportUserDataHandler to export all user data
- Add GET /api/v1/export endpoint with gzip compression
- Apply strict rate limiting (1 export per hour per user)
- Export includes all habits (active + archived) and entries
- Add i18n translations for export errors (en/es)
- Update all test mocks to implement new repository method
- Export format: JSON with gzip (~10x compression ratio)
2025-12-03 22:32:22 +01:00

105 lines
3.0 KiB
Go

package queries
import (
"context"
"time"
"apocapoc-api/internal/domain/repositories"
"apocapoc-api/internal/domain/value_objects"
)
type ExportHabitDTO struct {
ID string `json:"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 ExportEntryDTO 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 ExportUserDataResult struct {
ExportedAt time.Time `json:"exported_at"`
Habits []ExportHabitDTO `json:"habits"`
Entries []ExportEntryDTO `json:"entries"`
}
type ExportUserDataQuery struct {
UserID string
}
type ExportUserDataHandler struct {
habitRepo repositories.HabitRepository
entryRepo repositories.HabitEntryRepository
}
func NewExportUserDataHandler(
habitRepo repositories.HabitRepository,
entryRepo repositories.HabitEntryRepository,
) *ExportUserDataHandler {
return &ExportUserDataHandler{
habitRepo: habitRepo,
entryRepo: entryRepo,
}
}
func (h *ExportUserDataHandler) Handle(ctx context.Context, query ExportUserDataQuery) (*ExportUserDataResult, error) {
habits, err := h.habitRepo.FindByUserID(ctx, query.UserID)
if err != nil {
return nil, err
}
entries, err := h.entryRepo.FindByUserID(ctx, query.UserID)
if err != nil {
return nil, err
}
habitDTOs := make([]ExportHabitDTO, 0, len(habits))
for _, habit := range habits {
habitDTOs = append(habitDTOs, ExportHabitDTO{
ID: habit.ID,
Name: habit.Name,
Description: habit.Description,
Type: habit.Type,
Frequency: habit.Frequency,
SpecificDays: habit.SpecificDays,
SpecificDates: habit.SpecificDates,
CarryOver: habit.CarryOver,
IsNegative: habit.IsNegative,
TargetValue: habit.TargetValue,
CreatedAt: habit.CreatedAt,
ArchivedAt: habit.ArchivedAt,
})
}
entryDTOs := make([]ExportEntryDTO, 0, len(entries))
for _, entry := range entries {
entryDTOs = append(entryDTOs, ExportEntryDTO{
ID: entry.ID,
HabitID: entry.HabitID,
ScheduledDate: entry.ScheduledDate,
CompletedAt: entry.CompletedAt,
Value: entry.Value,
})
}
return &ExportUserDataResult{
ExportedAt: time.Now(),
Habits: habitDTOs,
Entries: entryDTOs,
}, nil
}