feat(backend): migrate notification IDs from int to string and update cursor format

Change notification ID type from int to string (CHAR(12)) to match the schema migration.

- Update AdminNotification.ID field to string
- Change cursor format from int-based to string-based for pagination
- Use validators.IsValidID instead of strconv.Atoi for notification ID validation
- Update cursor format to use RFC3339Nano for precision

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-06-20 16:58:51 +01:00
co-authored by Sisyphus
parent 0beaa6e117
commit fb910bf60d
@@ -18,7 +18,7 @@ import (
// Structs returned in JSON // Structs returned in JSON
type AdminNotification struct { type AdminNotification struct {
ID int `json:"id"` ID string `json:"id"`
Reason string `json:"reason"` Reason string `json:"reason"`
BookingID *string `json:"booking_id,omitempty"` BookingID *string `json:"booking_id,omitempty"`
UserID *string `json:"user_id,omitempty"` UserID *string `json:"user_id,omitempty"`
@@ -185,7 +185,7 @@ func GetNotifications(w http.ResponseWriter, r *http.Request) {
if len(notifications) > perPage { if len(notifications) > perPage {
notifications = notifications[:perPage] notifications = notifications[:perPage]
last := notifications[len(notifications)-1] last := notifications[len(notifications)-1]
cursor := last.CreatedAt.Format(time.RFC3339) + "|" + strconv.Itoa(last.ID) cursor := last.CreatedAt.Format(time.RFC3339Nano) + "|" + last.ID
nextCursor = &cursor nextCursor = &cursor
} }
@@ -227,12 +227,7 @@ func GetUnreadCount(w http.ResponseWriter, r *http.Request) {
func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) { func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id") idStr := chi.URLParam(r, "id")
if idStr == "" { if idStr == "" || !validators.IsValidID(idStr) {
http.Error(w, "missing notification ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
http.Error(w, "invalid notification ID", http.StatusBadRequest) http.Error(w, "invalid notification ID", http.StatusBadRequest)
return return
} }
@@ -243,9 +238,9 @@ func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) {
WHERE id = $1 AND acknowledged_at IS NULL WHERE id = $1 AND acknowledged_at IS NULL
` `
cmdTag, err := db.DB.Exec(r.Context(), query, id) cmdTag, err := db.DB.Exec(r.Context(), query, idStr)
if err != nil { if err != nil {
log.Printf("Failed to acknowledge notification %d: %v", id, err) log.Printf("Failed to acknowledge notification %s: %v", idStr, err)
http.Error(w, "Internal server error", http.StatusInternalServerError) http.Error(w, "Internal server error", http.StatusInternalServerError)
return return
} }