Files
Crussell/backend/handlers/user/gdpr_export.go
T
popertotsandSisyphus e86248b27c refactor: move auth, GDPR, and booking cleanup to centralized scheduler
Convert CleanupRevokedJTIs to return (int, error) and remove StartJTICleanup goroutine. Add CleanupStaleLoginEntries and CleanupGDPRExportCache for centralized scheduler. Add clock.London timezone location.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-07 00:10:00 +01:00

95 lines
2.1 KiB
Go

package user
import (
"context"
"encoding/json"
"log"
"net/http"
"sync"
"time"
"crussell/db"
"crussell/clock"
"crussell/mw"
)
var (
gdprExportCache = make(map[string]*gdprCacheEntry)
gdprExportCacheMu sync.RWMutex
)
type gdprCacheEntry struct {
data json.RawMessage
expiresAt time.Time
generating bool
}
// CleanupGDPRExportCache removes expired entries from the GDPR export cache.
// Called by the centralised jobs scheduler.
func CleanupGDPRExportCache(ctx context.Context) (int, error) {
gdprExportCacheMu.Lock()
defer gdprExportCacheMu.Unlock()
now := clock.Now()
var n int
for k, v := range gdprExportCache {
if now.After(v.expiresAt) {
delete(gdprExportCache, k)
n++
}
}
return n, nil
}
// GET /api/user/gdpr-export
func GetGDPRExportHandler(w http.ResponseWriter, r *http.Request) {
userID, ok := mw.GetUserID(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
gdprExportCacheMu.Lock()
if entry, found := gdprExportCache[userID]; found && clock.Now().Before(entry.expiresAt) {
if entry.generating {
gdprExportCacheMu.Unlock()
w.Header().Set("X-Cache", "GENERATING")
w.Write([]byte(`{"status":"generating"}`))
return
}
gdprExportCacheMu.Unlock()
w.Header().Set("X-Cache", "HIT")
w.Write(entry.data)
return
}
gdprExportCache[userID] = &gdprCacheEntry{generating: true}
gdprExportCacheMu.Unlock()
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic recovered in GDPR export query: %v", r)
}
}()
var result json.RawMessage
err := db.Conn.QueryRow(context.Background(), `SELECT export_all_user_data($1)`, userID).Scan(&result)
if err != nil {
log.Printf("GDPR export failed for user %s: %v", userID, err)
gdprExportCacheMu.Lock()
delete(gdprExportCache, userID)
gdprExportCacheMu.Unlock()
return
}
gdprExportCacheMu.Lock()
gdprExportCache[userID] = &gdprCacheEntry{
data: result,
expiresAt: clock.Now().Add(12 * time.Hour),
}
gdprExportCacheMu.Unlock()
}()
w.Header().Set("X-Cache", "MISS")
w.Write([]byte(`{"status":"generating"}`))
}