From 568b4ebb88c751c9cb77f3526b82456f8cfd62be Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Wed, 24 Dec 2025 11:29:55 -0300 Subject: [PATCH] refactor: clean up legacy UUID v4, use UUID v7 everywhere Migrations: - 016, 017, 019: Replace gen_random_uuid() with uuid_generate_v7() - All UUID tables now use custom uuid_generate_v7() function Backend: - Create internal/utils/uuid/uuid.go with V7() function (RFC 9562) - Update storage_handler.go to use internal uuid.V7() - Remove dependency on google/uuid for file naming All new UUIDs in the system are now UUID v7 (time-ordered) --- backend/internal/handlers/storage_handler.go | 4 +- backend/internal/utils/uuid/uuid.go | 64 +++++++++++++++++++ .../016_create_notifications_table.sql | 2 +- .../migrations/017_create_tickets_table.sql | 4 +- .../019_create_job_payments_table.sql | 2 +- 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 backend/internal/utils/uuid/uuid.go diff --git a/backend/internal/handlers/storage_handler.go b/backend/internal/handlers/storage_handler.go index a4d2431..eac71d7 100644 --- a/backend/internal/handlers/storage_handler.go +++ b/backend/internal/handlers/storage_handler.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/google/uuid" "github.com/rede5/gohorsejobs/backend/internal/infrastructure/storage" + "github.com/rede5/gohorsejobs/backend/internal/utils/uuid" ) // StorageHandler handles file storage operations @@ -111,7 +111,7 @@ func (h *StorageHandler) GenerateUploadURL(w http.ResponseWriter, r *http.Reques // Generate unique key ext := filepath.Ext(req.Filename) - uniqueID := uuid.New().String() + uniqueID := uuid.V7() timestamp := time.Now().Format("20060102") key := fmt.Sprintf("%s/%s/%s%s", folder, timestamp, uniqueID, ext) diff --git a/backend/internal/utils/uuid/uuid.go b/backend/internal/utils/uuid/uuid.go new file mode 100644 index 0000000..6d453b4 --- /dev/null +++ b/backend/internal/utils/uuid/uuid.go @@ -0,0 +1,64 @@ +package uuid + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "time" +) + +// V7 generates a UUID v7 (time-ordered) following RFC 9562 +// Format: tttttttt-tttt-7xxx-yxxx-xxxxxxxxxxxx +// Where: t = timestamp (48 bits), 7 = version, y = variant, x = random +func V7() string { + // Get current timestamp in milliseconds + timestamp := time.Now().UnixMilli() + + // Generate 10 random bytes + randomBytes := make([]byte, 10) + rand.Read(randomBytes) + + // Build UUID bytes (16 total) + var bytes [16]byte + + // First 6 bytes: timestamp (48 bits, big-endian) + binary.BigEndian.PutUint32(bytes[0:4], uint32(timestamp>>16)) + binary.BigEndian.PutUint16(bytes[4:6], uint16(timestamp)) + + // Next 2 bytes: version (4 bits = 7) + random (12 bits) + bytes[6] = 0x70 | (randomBytes[0] & 0x0F) // version 7 + bytes[7] = randomBytes[1] + + // Last 8 bytes: variant (2 bits = 10) + random (62 bits) + bytes[8] = 0x80 | (randomBytes[2] & 0x3F) // variant RFC 4122 + copy(bytes[9:16], randomBytes[3:10]) + + // Format as UUID string + return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", + binary.BigEndian.Uint32(bytes[0:4]), + binary.BigEndian.Uint16(bytes[4:6]), + binary.BigEndian.Uint16(bytes[6:8]), + binary.BigEndian.Uint16(bytes[8:10]), + bytes[10:16], + ) +} + +// IsValid checks if a string is a valid UUID (any version) +func IsValid(s string) bool { + // Basic UUID format check: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if len(s) != 36 { + return false + } + for i, c := range s { + if i == 8 || i == 13 || i == 18 || i == 23 { + if c != '-' { + return false + } + } else { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + return false + } + } + } + return true +} diff --git a/backend/migrations/016_create_notifications_table.sql b/backend/migrations/016_create_notifications_table.sql index 540921f..5009600 100644 --- a/backend/migrations/016_create_notifications_table.sql +++ b/backend/migrations/016_create_notifications_table.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS notifications ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, type VARCHAR(50) NOT NULL, -- info, success, warning, error title VARCHAR(255) NOT NULL, diff --git a/backend/migrations/017_create_tickets_table.sql b/backend/migrations/017_create_tickets_table.sql index 3047308..49ca11e 100644 --- a/backend/migrations/017_create_tickets_table.sql +++ b/backend/migrations/017_create_tickets_table.sql @@ -2,7 +2,7 @@ DROP TABLE IF EXISTS ticket_messages; DROP TABLE IF EXISTS tickets; CREATE TABLE tickets ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, subject VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL DEFAULT 'open', -- open, in_progress, closed @@ -15,7 +15,7 @@ CREATE INDEX idx_tickets_user_id ON tickets(user_id); CREATE INDEX idx_tickets_status ON tickets(status); CREATE TABLE ticket_messages ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), ticket_id UUID NOT NULL REFERENCES tickets(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- Sender message TEXT NOT NULL, diff --git a/backend/migrations/019_create_job_payments_table.sql b/backend/migrations/019_create_job_payments_table.sql index 1f74766..883e69b 100644 --- a/backend/migrations/019_create_job_payments_table.sql +++ b/backend/migrations/019_create_job_payments_table.sql @@ -2,7 +2,7 @@ -- Description: Track payments for job postings CREATE TABLE IF NOT EXISTS job_payments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), job_id INT NOT NULL, user_id INT,