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)
This commit is contained in:
Tiago Yamamoto 2025-12-24 11:29:55 -03:00
parent 7f30a214f7
commit 568b4ebb88
5 changed files with 70 additions and 6 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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,

View file

@ -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,

View file

@ -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,