feat: wire reserve route, dual TTL cleanup, and booking reservation deletion

Add POST /api/bookings/reserve route with OptionalAuth middleware and rate limiting.
Update CleanupOldReservations to handle dual TTLs: 1hr for logged-in users,
10min for anonymous reservations. Update CreateBookingHandler to also match
anon reservations by start_time for users who register mid-flow.
Add comprehensive tests for reservation creation, validation, conflict detection, and cleanup.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-29 23:05:48 +01:00
co-authored by Sisyphus
parent 2ba7b85d0b
commit 6766c6128c
4 changed files with 421 additions and 32 deletions
+19 -17
View File
@@ -19,20 +19,20 @@ import (
// --- Types ---
type TimeBlocker struct {
ID string `json:"id"`
StartTime time.Time `json:"start_time"`
DurationMinutes int `json:"duration_minutes"`
Description string `json:"description,omitempty"`
CronExpression *string `json:"cron_expression,omitempty"`
CreatedAt time.Time `json:"created_at"`
CreatedBy *string `json:"created_by,omitempty"`
ID string `json:"id"`
StartTime time.Time `json:"start_time"`
DurationMinutes int `json:"duration_minutes"`
Description string `json:"description,omitempty"`
CronExpression *string `json:"cron_expression,omitempty"`
CreatedAt time.Time `json:"created_at"`
CreatedBy *string `json:"created_by,omitempty"`
}
type CreateTimeBlockerRequest struct {
StartTime time.Time `json:"start_time"`
DurationMinutes int `json:"duration_minutes"`
Description string `json:"description,omitempty"`
CronExpression *string `json:"cron_expression,omitempty"`
StartTime time.Time `json:"start_time"`
DurationMinutes int `json:"duration_minutes"`
Description string `json:"description,omitempty"`
CronExpression *string `json:"cron_expression,omitempty"`
}
// --- List Time Blockers ---
@@ -185,7 +185,6 @@ func DeleteTimeBlocker(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// --- Helper: Get Time Blockers in Range ---
// --- Helper: Get Time Blockers in Range ---
@@ -334,14 +333,17 @@ func CheckTimeBlockerOverlap(ctx context.Context, startTime, endTime time.Time)
return false, "", nil
}
// CleanupOldReservations deletes reservations (time_blockers with RESERVATION: description prefix)
// that are older than 1 hour.
// CleanupOldReservations deletes expired reservations:
// - Logged-in (RESERVATION:user): older than 1 hour
// - Anonymous (RESERVATION:anon): older than 10 minutes
func CleanupOldReservations(ctx context.Context) error {
oneHourAgo := time.Now().Add(-1 * time.Hour)
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
_, err := db.DB.Exec(ctx, `
DELETE FROM time_blockers
WHERE description LIKE 'RESERVATION:%'
AND created_at < $1
`, oneHourAgo)
WHERE (description LIKE 'RESERVATION:user:%' AND created_at < $1)
OR (description LIKE 'RESERVATION:anon:%' AND created_at < $2)
`, oneHourAgo, tenMinutesAgo)
return err
}