feat: dual TTL reservation cleanup and guest account anonymization
CleanupOldReservations now handles four reservation types: logged-in (1hr), anonymous (10min), admin-walkin (10min), admin-callin (1hr). AnonymizeStaleGuestAccounts scrubs PII from guest accounts whose last booking was 6+ months ago and who have no pending/confirmed bookings. Financial records remain intact — only personal data is wiped (UK GDPR compliance). Triggered on every GetAvailableHours call. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -275,6 +275,11 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("Failed to cleanup old reservations: %v", err)
|
log.Printf("Failed to cleanup old reservations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anonymize stale guest accounts (6+ months after last booking)
|
||||||
|
if err := AnonymizeStaleGuestAccounts(r.Context()); err != nil {
|
||||||
|
log.Printf("Failed to anonymize stale guest accounts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Load default hours
|
// Load default hours
|
||||||
defaultMap := map[int]DefaultHours{}
|
defaultMap := map[int]DefaultHours{}
|
||||||
defRows, _ := db.DB.Query(r.Context(), `SELECT weekday, start_time::text, end_time::text, is_open FROM working_hours`)
|
defRows, _ := db.DB.Query(r.Context(), `SELECT weekday, start_time::text, end_time::text, is_open FROM working_hours`)
|
||||||
|
|||||||
@@ -336,6 +336,8 @@ func CheckTimeBlockerOverlap(ctx context.Context, startTime, endTime time.Time)
|
|||||||
// CleanupOldReservations deletes expired reservations:
|
// CleanupOldReservations deletes expired reservations:
|
||||||
// - Logged-in (RESERVATION:user): older than 1 hour
|
// - Logged-in (RESERVATION:user): older than 1 hour
|
||||||
// - Anonymous (RESERVATION:anon): older than 10 minutes
|
// - Anonymous (RESERVATION:anon): older than 10 minutes
|
||||||
|
// - Admin walk-in (RESERVATION:admin:walkin:%): older than 10 minutes
|
||||||
|
// - Admin call-in (RESERVATION:admin:callin:%): older than 1 hour
|
||||||
func CleanupOldReservations(ctx context.Context) error {
|
func CleanupOldReservations(ctx context.Context) error {
|
||||||
oneHourAgo := time.Now().Add(-1 * time.Hour)
|
oneHourAgo := time.Now().Add(-1 * time.Hour)
|
||||||
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
|
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
|
||||||
@@ -344,6 +346,36 @@ func CleanupOldReservations(ctx context.Context) error {
|
|||||||
DELETE FROM time_blockers
|
DELETE FROM time_blockers
|
||||||
WHERE (description LIKE 'RESERVATION:user:%' AND created_at < $1)
|
WHERE (description LIKE 'RESERVATION:user:%' AND created_at < $1)
|
||||||
OR (description LIKE 'RESERVATION:anon:%' AND created_at < $2)
|
OR (description LIKE 'RESERVATION:anon:%' AND created_at < $2)
|
||||||
`, oneHourAgo, tenMinutesAgo)
|
OR (description LIKE 'RESERVATION:admin:walkin:%' AND created_at < $2)
|
||||||
|
OR (description LIKE 'RESERVATION:admin:callin:%' AND created_at < $3)
|
||||||
|
`, oneHourAgo, tenMinutesAgo, oneHourAgo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnonymizeStaleGuestAccounts anonymizes personal data for guest accounts
|
||||||
|
// whose last booking was more than 6 months ago (UK GDPR storage limitation).
|
||||||
|
// Financial records (bookings, payments) remain intact — only PII is scrubbed.
|
||||||
|
// Active/pending bookings are excluded so the salon can still contact the guest.
|
||||||
|
func AnonymizeStaleGuestAccounts(ctx context.Context) error {
|
||||||
|
_, err := db.DB.Exec(ctx, `
|
||||||
|
UPDATE users SET
|
||||||
|
n_first_name = 'Guest',
|
||||||
|
n_last_name = 'Anonymized',
|
||||||
|
email = 'anon-' || id || '@anon.invalid',
|
||||||
|
phone = '000000000000',
|
||||||
|
date_of_birth = '1900-01-01',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE account_role = 'guest'
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT user_id FROM bookings WHERE status IN ('pending', 'confirmed')
|
||||||
|
)
|
||||||
|
AND id IN (
|
||||||
|
SELECT user_id
|
||||||
|
FROM bookings
|
||||||
|
WHERE user_id IS NOT NULL
|
||||||
|
GROUP BY user_id
|
||||||
|
HAVING MAX(start_time) < NOW() - INTERVAL '6 months'
|
||||||
|
)
|
||||||
|
`)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user