Files
Crussell/backend/handlers/user/gdpr_export.go
T
popertotsandSisyphus 3d0e2afc4c refactor(backend): migrate db.DB to db.Conn PoolProxy across all handlers
Replace direct *pgxpool.Pool usage with PoolProxy wrapper across the entire backend:

- db.DB renamed to db.Conn (*pgxpool.Pool -> *PoolProxy)
- JWT functions now accept context.Context instead of using context.Background()
- Handler DB calls route through PoolProxy for per-test transaction support
- Fixture/helper/testdb functions accept Querier interface for decoupling
- Query ordering fixed in bookings handlers: COUNT after data query to avoid pgx conn busy
- Time truncation fixed: time.Date instead of Truncate(24*time.Hour) for week start calc
- testmain_test.go files updated with SeedBaseline and NewPoolProxy

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-21 19:28:54 +01:00

93 lines
2.0 KiB
Go

package user
import (
"context"
"encoding/json"
"log"
"net/http"
"sync"
"time"
"crussell/db"
"crussell/mw"
)
var (
gdprExportCache = make(map[string]*gdprCacheEntry)
gdprExportCacheMu sync.RWMutex
)
type gdprCacheEntry struct {
data json.RawMessage
expiresAt time.Time
generating bool
}
func init() {
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
gdprExportCacheMu.Lock()
now := time.Now()
for k, v := range gdprExportCache {
if now.After(v.expiresAt) {
delete(gdprExportCache, k)
}
}
gdprExportCacheMu.Unlock()
}
}()
}
// 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 && time.Now().Before(entry.expiresAt) {
if entry.generating {
gdprExportCacheMu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "GENERATING")
w.Write([]byte(`{"status":"generating"}`))
return
}
gdprExportCacheMu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "HIT")
w.Write(entry.data)
return
}
gdprExportCache[userID] = &gdprCacheEntry{generating: true}
gdprExportCacheMu.Unlock()
go func() {
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: time.Now().Add(12 * time.Hour),
}
gdprExportCacheMu.Unlock()
}()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "MISS")
w.Write([]byte(`{"status":"generating"}`))
}