diff --git a/internal/application/commands/unmark_habit.go b/internal/application/commands/unmark_habit.go index e565ffc..eb70924 100644 --- a/internal/application/commands/unmark_habit.go +++ b/internal/application/commands/unmark_habit.go @@ -4,7 +4,6 @@ import ( "context" "time" - "apocapoc-api/internal/domain/entities" "apocapoc-api/internal/domain/repositories" "apocapoc-api/internal/shared/errors" ) @@ -57,22 +56,19 @@ func (h *UnmarkHabitHandler) Handle(ctx context.Context, cmd UnmarkHabitCommand) return err } - // Find the active entry for this date - var targetEntry *entities.HabitEntry + // Find the entry for this date + var targetEntryID string for _, entry := range entries { - if entry.ScheduledDate.Equal(cmd.ScheduledDate) && entry.DeletedAt == nil { - targetEntry = entry + if entry.ScheduledDate.Equal(cmd.ScheduledDate) { + targetEntryID = entry.ID break } } - if targetEntry == nil { + if targetEntryID == "" { return errors.ErrNotFound } - // Soft delete the entry - now := time.Now() - targetEntry.DeletedAt = &now - - return h.entryRepo.Update(ctx, targetEntry) + // Hard delete the entry + return h.entryRepo.Delete(ctx, targetEntryID) } diff --git a/internal/application/commands/unmark_habit_test.go b/internal/application/commands/unmark_habit_test.go index 8b16bdc..36e23da 100644 --- a/internal/application/commands/unmark_habit_test.go +++ b/internal/application/commands/unmark_habit_test.go @@ -12,20 +12,20 @@ import ( type mockEntryRepoForUnmark struct { mockEntryRepo - entries []*entities.HabitEntry - updatedEntry *entities.HabitEntry - errorOnUpdate error + entries []*entities.HabitEntry + deletedEntryID string + errorOnDelete error } func (m *mockEntryRepoForUnmark) FindByHabitIDAndDateRange(ctx context.Context, habitID string, from, to time.Time) ([]*entities.HabitEntry, error) { return m.entries, nil } -func (m *mockEntryRepoForUnmark) Update(ctx context.Context, entry *entities.HabitEntry) error { - if m.errorOnUpdate != nil { - return m.errorOnUpdate +func (m *mockEntryRepoForUnmark) Delete(ctx context.Context, id string) error { + if m.errorOnDelete != nil { + return m.errorOnDelete } - m.updatedEntry = entry + m.deletedEntryID = id return nil } @@ -59,12 +59,8 @@ func TestUnmarkHabitHandler_UnmarksSuccessfully(t *testing.T) { t.Fatalf("Expected no error, got %v", err) } - if entryRepo.updatedEntry == nil { - t.Fatal("Expected entry to be updated") - } - - if entryRepo.updatedEntry.DeletedAt == nil { - t.Error("Expected entry to be soft deleted") + if entryRepo.deletedEntryID != "entry-1" { + t.Errorf("Expected entry entry-1 to be deleted, got %s", entryRepo.deletedEntryID) } } @@ -143,36 +139,3 @@ func TestUnmarkHabitHandler_ReturnsErrorWhenEntryNotFound(t *testing.T) { } } -func TestUnmarkHabitHandler_IgnoresAlreadyDeletedEntry(t *testing.T) { - habit := entities.NewHabit("user-123", "Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false) - habit.ID = "habit-1" - - scheduledDate := time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC) - entry := entities.NewHabitEntry("habit-1", scheduledDate, nil) - entry.ID = "entry-1" - now := time.Now() - entry.DeletedAt = &now // Already deleted - - habitRepo := &mockHabitRepoForUpdate{ - habitToReturn: habit, - } - - entryRepo := &mockEntryRepoForUnmark{ - entries: []*entities.HabitEntry{entry}, - } - - handler := NewUnmarkHabitHandler(habitRepo, entryRepo) - - cmd := UnmarkHabitCommand{ - HabitID: "habit-1", - UserID: "user-123", - ScheduledDate: scheduledDate, - } - - err := handler.Handle(context.Background(), cmd) - - // Should return not found since the active entry doesn't exist - if err != errors.ErrNotFound { - t.Errorf("Expected ErrNotFound for already deleted entry, got %v", err) - } -} diff --git a/internal/application/queries/get_habit_entries.go b/internal/application/queries/get_habit_entries.go index 7f8a000..a23e7e2 100644 --- a/internal/application/queries/get_habit_entries.go +++ b/internal/application/queries/get_habit_entries.go @@ -4,6 +4,7 @@ import ( "context" "time" + "apocapoc-api/internal/domain/entities" "apocapoc-api/internal/domain/repositories" "apocapoc-api/internal/shared/errors" ) @@ -19,6 +20,17 @@ type HabitEntryDTO struct { type GetHabitEntriesQuery struct { HabitID string UserID string + From *time.Time + To *time.Time + Page int + Limit int +} + +type GetHabitEntriesResult struct { + Entries []HabitEntryDTO + Total int + Page int + Limit int } type GetHabitEntriesHandler struct { @@ -36,8 +48,7 @@ func NewGetHabitEntriesHandler( } } -func (h *GetHabitEntriesHandler) Handle(ctx context.Context, query GetHabitEntriesQuery) ([]HabitEntryDTO, error) { - // Verify habit exists and user owns it +func (h *GetHabitEntriesHandler) Handle(ctx context.Context, query GetHabitEntriesQuery) (*GetHabitEntriesResult, error) { habit, err := h.habitRepo.FindByID(ctx, query.HabitID) if err != nil { return nil, err @@ -47,19 +58,58 @@ func (h *GetHabitEntriesHandler) Handle(ctx context.Context, query GetHabitEntri return nil, errors.ErrUnauthorized } - // Get all entries for the habit - entries, err := h.entryRepo.FindByHabitID(ctx, query.HabitID) + var dateRangeDays int + if query.From != nil && query.To != nil { + dateRangeDays = int(query.To.Sub(*query.From).Hours() / 24) + } + + requiresPagination := false + if query.From == nil || query.To == nil { + requiresPagination = true + } else if dateRangeDays > 365 { + requiresPagination = true + } + + if requiresPagination && query.Limit == 0 { + return nil, errors.ErrInvalidInput + } + + var entries []*entities.HabitEntry + + if query.From != nil && query.To != nil { + entries, err = h.entryRepo.FindByHabitIDAndDateRange(ctx, query.HabitID, *query.From, *query.To) + } else if query.From != nil { + entries, err = h.entryRepo.FindByHabitIDAndDateRange(ctx, query.HabitID, *query.From, time.Now()) + } else if query.To != nil { + entries, err = h.entryRepo.FindByHabitIDAndDateRange(ctx, query.HabitID, time.Time{}, *query.To) + } else { + entries, err = h.entryRepo.FindByHabitID(ctx, query.HabitID) + } + if err != nil { return nil, err } + total := len(entries) + + if query.Limit > 0 { + offset := (query.Page - 1) * query.Limit + if offset < 0 { + offset = 0 + } + end := offset + query.Limit + if offset < len(entries) { + if end > len(entries) { + end = len(entries) + } + entries = entries[offset:end] + } else { + entries = []*entities.HabitEntry{} + } + } + var result []HabitEntryDTO for _, entry := range entries { - // Filter out deleted entries - if entry.DeletedAt != nil { - continue - } - result = append(result, HabitEntryDTO{ ID: entry.ID, HabitID: entry.HabitID, @@ -69,5 +119,10 @@ func (h *GetHabitEntriesHandler) Handle(ctx context.Context, query GetHabitEntri }) } - return result, nil + return &GetHabitEntriesResult{ + Entries: result, + Total: total, + Page: query.Page, + Limit: query.Limit, + }, nil } diff --git a/internal/application/queries/get_habit_entries_test.go b/internal/application/queries/get_habit_entries_test.go index 43b502c..f00e667 100644 --- a/internal/application/queries/get_habit_entries_test.go +++ b/internal/application/queries/get_habit_entries_test.go @@ -2,6 +2,7 @@ package queries import ( "context" + "strconv" "testing" "time" @@ -45,24 +46,30 @@ func TestGetHabitEntriesHandler_ReturnsEntriesSuccessfully(t *testing.T) { query := GetHabitEntriesQuery{ HabitID: "habit-1", UserID: "user-123", + Page: 1, + Limit: 50, } - results, err := handler.Handle(context.Background(), query) + result, err := handler.Handle(context.Background(), query) if err != nil { t.Fatalf("Expected no error, got %v", err) } - if len(results) != 2 { - t.Fatalf("Expected 2 entries, got %d", len(results)) + if len(result.Entries) != 2 { + t.Fatalf("Expected 2 entries, got %d", len(result.Entries)) } - if results[0].ID != "entry-1" { - t.Errorf("Expected first entry ID entry-1, got %s", results[0].ID) + if result.Entries[0].ID != "entry-1" { + t.Errorf("Expected first entry ID entry-1, got %s", result.Entries[0].ID) } - if results[1].ID != "entry-2" { - t.Errorf("Expected second entry ID entry-2, got %s", results[1].ID) + if result.Entries[1].ID != "entry-2" { + t.Errorf("Expected second entry ID entry-2, got %s", result.Entries[1].ID) + } + + if result.Total != 2 { + t.Errorf("Expected total 2, got %d", result.Total) } } @@ -78,6 +85,8 @@ func TestGetHabitEntriesHandler_ReturnsErrorWhenHabitNotFound(t *testing.T) { query := GetHabitEntriesQuery{ HabitID: "non-existent", UserID: "user-123", + Page: 1, + Limit: 50, } _, err := handler.Handle(context.Background(), query) @@ -101,7 +110,9 @@ func TestGetHabitEntriesHandler_ReturnsErrorWhenUserDoesNotOwnHabit(t *testing.T query := GetHabitEntriesQuery{ HabitID: "habit-1", - UserID: "user-456", // Different user + UserID: "user-456", + Page: 1, + Limit: 50, } _, err := handler.Handle(context.Background(), query) @@ -111,27 +122,24 @@ func TestGetHabitEntriesHandler_ReturnsErrorWhenUserDoesNotOwnHabit(t *testing.T } } -func TestGetHabitEntriesHandler_FiltersDeletedEntries(t *testing.T) { +func TestGetHabitEntriesHandler_WithPagination(t *testing.T) { habit := entities.NewHabit("user-123", "Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false) habit.ID = "habit-1" - date1 := time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC) - date2 := time.Date(2025, 1, 16, 0, 0, 0, 0, time.UTC) - - entry1 := entities.NewHabitEntry("habit-1", date1, nil) - entry1.ID = "entry-1" - - entry2 := entities.NewHabitEntry("habit-1", date2, nil) - entry2.ID = "entry-2" - now := time.Now() - entry2.DeletedAt = &now // This one is deleted + entries := make([]*entities.HabitEntry, 10) + for i := 0; i < 10; i++ { + date := time.Date(2025, 1, i+1, 0, 0, 0, 0, time.UTC) + entry := entities.NewHabitEntry("habit-1", date, nil) + entry.ID = "entry-" + strconv.Itoa(i+1) + entries[i] = entry + } habitRepo := &mockHabitRepoWithFindByID{ habitToReturn: habit, } entryRepo := &mockEntryRepoWithFindByHabitID{ - entries: []*entities.HabitEntry{entry1, entry2}, + entries: entries, } handler := NewGetHabitEntriesHandler(habitRepo, entryRepo) @@ -139,20 +147,131 @@ func TestGetHabitEntriesHandler_FiltersDeletedEntries(t *testing.T) { query := GetHabitEntriesQuery{ HabitID: "habit-1", UserID: "user-123", + Page: 2, + Limit: 3, } - results, err := handler.Handle(context.Background(), query) + result, err := handler.Handle(context.Background(), query) if err != nil { t.Fatalf("Expected no error, got %v", err) } - // Should only return the non-deleted entry - if len(results) != 1 { - t.Fatalf("Expected 1 active entry, got %d", len(results)) + if len(result.Entries) != 3 { + t.Fatalf("Expected 3 entries on page 2, got %d", len(result.Entries)) } - if results[0].ID != "entry-1" { - t.Errorf("Expected entry-1 (non-deleted), got %s", results[0].ID) + if result.Total != 10 { + t.Errorf("Expected total 10, got %d", result.Total) + } + + if result.Page != 2 { + t.Errorf("Expected page 2, got %d", result.Page) + } + + if result.Limit != 3 { + t.Errorf("Expected limit 3, got %d", result.Limit) } } + +func TestGetHabitEntriesHandler_RequiresPaginationWithoutDateRange(t *testing.T) { + habit := entities.NewHabit("user-123", "Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false) + habit.ID = "habit-1" + + habitRepo := &mockHabitRepoWithFindByID{ + habitToReturn: habit, + } + + entryRepo := &mockEntryRepoWithFindByHabitID{ + entries: []*entities.HabitEntry{}, + } + + handler := NewGetHabitEntriesHandler(habitRepo, entryRepo) + + query := GetHabitEntriesQuery{ + HabitID: "habit-1", + UserID: "user-123", + Page: 0, + Limit: 0, + } + + _, err := handler.Handle(context.Background(), query) + + if err != errors.ErrInvalidInput { + t.Errorf("Expected ErrInvalidInput when pagination not provided without date range, got %v", err) + } +} + +func TestGetHabitEntriesHandler_AllowsNoPaginationWithShortDateRange(t *testing.T) { + habit := entities.NewHabit("user-123", "Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false) + habit.ID = "habit-1" + + from := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + to := time.Date(2025, 6, 30, 0, 0, 0, 0, time.UTC) + + entry := entities.NewHabitEntry("habit-1", from, nil) + entry.ID = "entry-1" + + habitRepo := &mockHabitRepoWithFindByID{ + habitToReturn: habit, + } + + entryRepo := &mockEntryRepoWithFindByHabitID{ + entries: []*entities.HabitEntry{entry}, + } + + handler := NewGetHabitEntriesHandler(habitRepo, entryRepo) + + query := GetHabitEntriesQuery{ + HabitID: "habit-1", + UserID: "user-123", + From: &from, + To: &to, + Page: 0, + Limit: 0, + } + + result, err := handler.Handle(context.Background(), query) + + if err != nil { + t.Fatalf("Expected no error for short date range without pagination, got %v", err) + } + + if len(result.Entries) != 1 { + t.Errorf("Expected 1 entry, got %d", len(result.Entries)) + } +} + +func TestGetHabitEntriesHandler_RequiresPaginationWithLongDateRange(t *testing.T) { + habit := entities.NewHabit("user-123", "Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false) + habit.ID = "habit-1" + + from := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + to := time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC) + + habitRepo := &mockHabitRepoWithFindByID{ + habitToReturn: habit, + } + + entryRepo := &mockEntryRepoWithFindByHabitID{ + entries: []*entities.HabitEntry{}, + } + + handler := NewGetHabitEntriesHandler(habitRepo, entryRepo) + + query := GetHabitEntriesQuery{ + HabitID: "habit-1", + UserID: "user-123", + From: &from, + To: &to, + Page: 0, + Limit: 0, + } + + _, err := handler.Handle(context.Background(), query) + + if err != errors.ErrInvalidInput { + t.Errorf("Expected ErrInvalidInput for date range > 1 year without pagination, got %v", err) + } +} + diff --git a/internal/application/queries/get_todays_habits.go b/internal/application/queries/get_todays_habits.go index 7df9f0c..274a235 100644 --- a/internal/application/queries/get_todays_habits.go +++ b/internal/application/queries/get_todays_habits.go @@ -70,7 +70,7 @@ func (h *GetTodaysHabitsHandler) Handle( isCompleted := false for _, entry := range entries { - if entry.ScheduledDate.Equal(query.Date) && entry.DeletedAt == nil { + if entry.ScheduledDate.Equal(query.Date) { isCompleted = true break } diff --git a/internal/domain/entities/habit_entry.go b/internal/domain/entities/habit_entry.go index 7ca380b..34c8133 100644 --- a/internal/domain/entities/habit_entry.go +++ b/internal/domain/entities/habit_entry.go @@ -8,7 +8,6 @@ type HabitEntry struct { ScheduledDate time.Time CompletedAt time.Time Value *float64 - DeletedAt *time.Time } func NewHabitEntry(habitID string, scheduledDate time.Time, value *float64) *HabitEntry { @@ -19,12 +18,3 @@ func NewHabitEntry(habitID string, scheduledDate time.Time, value *float64) *Hab Value: value, } } - -func (e *HabitEntry) Delete() { - now := time.Now() - e.DeletedAt = &now -} - -func (e *HabitEntry) IsDeleted() bool { - return e.DeletedAt != nil -} diff --git a/internal/infrastructure/http/dto.go b/internal/infrastructure/http/dto.go index 79f5ee5..8ae2974 100644 --- a/internal/infrastructure/http/dto.go +++ b/internal/infrastructure/http/dto.go @@ -69,6 +69,13 @@ type HabitEntryResponse struct { 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"` } diff --git a/internal/infrastructure/http/habit_handlers.go b/internal/infrastructure/http/habit_handlers.go index 3c3e1a4..c6a7fe0 100644 --- a/internal/infrastructure/http/habit_handlers.go +++ b/internal/infrastructure/http/habit_handlers.go @@ -3,6 +3,7 @@ package http import ( "encoding/json" "net/http" + "strconv" "time" "apocapoc-api/internal/application/commands" @@ -250,7 +251,64 @@ func (h *HabitHandlers) GetHabitEntries(w http.ResponseWriter, r *http.Request) UserID: userID, } - entries, err := h.getHabitEntriesHandler.Handle(r.Context(), query) + if fromStr := r.URL.Query().Get("from"); fromStr != "" { + from, err := time.Parse("2006-01-02", fromStr) + if err != nil { + respondError(w, http.StatusBadRequest, "Invalid 'from' date format (use YYYY-MM-DD)") + return + } + query.From = &from + } + + if toStr := r.URL.Query().Get("to"); toStr != "" { + to, err := time.Parse("2006-01-02", toStr) + if err != nil { + respondError(w, http.StatusBadRequest, "Invalid 'to' date format (use YYYY-MM-DD)") + return + } + query.To = &to + } + + var dateRangeDays int + if query.From != nil && query.To != nil { + dateRangeDays = int(query.To.Sub(*query.From).Hours() / 24) + } + + requiresPagination := false + if query.From == nil || query.To == nil { + requiresPagination = true + } else if dateRangeDays > 365 { + requiresPagination = true + } + + if pageStr := r.URL.Query().Get("page"); pageStr != "" { + page, err := strconv.Atoi(pageStr) + if err != nil || page < 1 { + respondError(w, http.StatusBadRequest, "Invalid 'page' parameter") + return + } + query.Page = page + } else if requiresPagination { + query.Page = 1 + } + + if limitStr := r.URL.Query().Get("limit"); limitStr != "" { + limit, err := strconv.Atoi(limitStr) + if err != nil || limit < 1 || limit > 100 { + respondError(w, http.StatusBadRequest, "Invalid 'limit' parameter (must be 1-100)") + return + } + query.Limit = limit + } else if requiresPagination { + query.Limit = 50 + } + + if requiresPagination && query.Limit == 0 { + respondError(w, http.StatusBadRequest, "Pagination required: provide 'limit' parameter or use date range \u2264 1 year") + return + } + + result, err := h.getHabitEntriesHandler.Handle(r.Context(), query) if err != nil { if err == errors.ErrNotFound { respondError(w, http.StatusNotFound, "Habit not found") @@ -264,9 +322,9 @@ func (h *HabitHandlers) GetHabitEntries(w http.ResponseWriter, r *http.Request) return } - response := make([]HabitEntryResponse, len(entries)) - for i, entry := range entries { - response[i] = HabitEntryResponse{ + entries := make([]HabitEntryResponse, len(result.Entries)) + for i, entry := range result.Entries { + entries[i] = HabitEntryResponse{ ID: entry.ID, HabitID: entry.HabitID, ScheduledDate: entry.ScheduledDate, @@ -275,6 +333,13 @@ func (h *HabitHandlers) GetHabitEntries(w http.ResponseWriter, r *http.Request) } } + response := HabitEntriesResponse{ + Entries: entries, + Total: result.Total, + Page: result.Page, + Limit: result.Limit, + } + respondJSON(w, http.StatusOK, response) } diff --git a/internal/infrastructure/persistence/sqlite/habit_entry_repository.go b/internal/infrastructure/persistence/sqlite/habit_entry_repository.go index e4212c0..1562667 100644 --- a/internal/infrastructure/persistence/sqlite/habit_entry_repository.go +++ b/internal/infrastructure/persistence/sqlite/habit_entry_repository.go @@ -52,7 +52,7 @@ func (r *HabitEntryRepository) FindByHabitIDAndDateRange( from, to time.Time, ) ([]*entities.HabitEntry, error) { query := ` - SELECT id, habit_id, scheduled_date, completed_at, value, deleted_at + SELECT id, habit_id, scheduled_date, completed_at, value FROM habit_entries WHERE habit_id = ? AND scheduled_date >= ? @@ -76,11 +76,11 @@ func (r *HabitEntryRepository) FindByHabitIDAndDateRange( func (r *HabitEntryRepository) Update(ctx context.Context, entry *entities.HabitEntry) error { query := ` UPDATE habit_entries - SET deleted_at = ? + SET value = ?, completed_at = ? WHERE id = ? ` - result, err := r.db.ExecContext(ctx, query, entry.DeletedAt, entry.ID) + result, err := r.db.ExecContext(ctx, query, entry.Value, entry.CompletedAt, entry.ID) if err != nil { return fmt.Errorf("failed to update entry: %w", err) } @@ -100,7 +100,6 @@ func (r *HabitEntryRepository) scanEntries(rows *sql.Rows) ([]*entities.HabitEnt var ( entry entities.HabitEntry scheduledDate string - deletedAt sql.NullTime ) err := rows.Scan( @@ -109,7 +108,6 @@ func (r *HabitEntryRepository) scanEntries(rows *sql.Rows) ([]*entities.HabitEnt &scheduledDate, &entry.CompletedAt, &entry.Value, - &deletedAt, ) if err != nil { @@ -125,10 +123,6 @@ func (r *HabitEntryRepository) scanEntries(rows *sql.Rows) ([]*entities.HabitEnt } entry.ScheduledDate = parsedDate - if deletedAt.Valid { - entry.DeletedAt = &deletedAt.Time - } - entries = append(entries, &entry) } @@ -137,7 +131,7 @@ func (r *HabitEntryRepository) scanEntries(rows *sql.Rows) ([]*entities.HabitEnt func (r *HabitEntryRepository) FindByID(ctx context.Context, id string) (*entities.HabitEntry, error) { query := ` - SELECT id, habit_id, scheduled_date, completed_at, value, deleted_at + SELECT id, habit_id, scheduled_date, completed_at, value FROM habit_entries WHERE id = ? ` @@ -145,7 +139,6 @@ func (r *HabitEntryRepository) FindByID(ctx context.Context, id string) (*entiti var ( entry entities.HabitEntry scheduledDate string - deletedAt sql.NullTime ) err := r.db.QueryRowContext(ctx, query, id).Scan( @@ -154,7 +147,6 @@ func (r *HabitEntryRepository) FindByID(ctx context.Context, id string) (*entiti &scheduledDate, &entry.CompletedAt, &entry.Value, - &deletedAt, ) if err == sql.ErrNoRows { @@ -173,16 +165,12 @@ func (r *HabitEntryRepository) FindByID(ctx context.Context, id string) (*entiti } entry.ScheduledDate = parsedDate - if deletedAt.Valid { - entry.DeletedAt = &deletedAt.Time - } - return &entry, nil } func (r *HabitEntryRepository) FindByHabitID(ctx context.Context, habitID string) ([]*entities.HabitEntry, error) { query := ` - SELECT id, habit_id, scheduled_date, completed_at, value, deleted_at + SELECT id, habit_id, scheduled_date, completed_at, value FROM habit_entries WHERE habit_id = ? ORDER BY scheduled_date DESC @@ -199,11 +187,10 @@ func (r *HabitEntryRepository) FindByHabitID(ctx context.Context, habitID string func (r *HabitEntryRepository) FindPendingByHabitID(ctx context.Context, habitID string, beforeDate time.Time) ([]*entities.HabitEntry, error) { query := ` - SELECT id, habit_id, scheduled_date, completed_at, value, deleted_at + SELECT id, habit_id, scheduled_date, completed_at, value FROM habit_entries WHERE habit_id = ? AND scheduled_date < ? - AND deleted_at IS NULL ORDER BY scheduled_date DESC ` diff --git a/internal/infrastructure/persistence/sqlite/migrations.go b/internal/infrastructure/persistence/sqlite/migrations.go index 2fb121e..e8b1353 100644 --- a/internal/infrastructure/persistence/sqlite/migrations.go +++ b/internal/infrastructure/persistence/sqlite/migrations.go @@ -56,7 +56,6 @@ CREATE TABLE IF NOT EXISTS habit_entries ( scheduled_date DATE NOT NULL, completed_at DATETIME NOT NULL, value REAL, - deleted_at DATETIME, FOREIGN KEY (habit_id) REFERENCES habits(id) ON DELETE CASCADE, UNIQUE(habit_id, scheduled_date) ); @@ -67,5 +66,4 @@ CREATE INDEX IF NOT EXISTS idx_habits_user ON habits(user_id); CREATE INDEX IF NOT EXISTS idx_habits_active ON habits(user_id, archived_at); CREATE INDEX IF NOT EXISTS idx_entries_habit ON habit_entries(habit_id); CREATE INDEX IF NOT EXISTS idx_entries_scheduled ON habit_entries(scheduled_date); -CREATE INDEX IF NOT EXISTS idx_entries_deleted ON habit_entries(deleted_at); `