package infra import ( "crypto/rand" "database/sql" "encoding/hex" "encoding/json" "fmt" "time" ) // sessionEnsureTable crea la tabla sessions si no existe. // Columnas: token (PK), user_id, expires_at, created_at, metadata (JSON). func sessionEnsureTable(db *sql.DB) error { const stmt = ` CREATE TABLE IF NOT EXISTS sessions ( token TEXT PRIMARY KEY, user_id TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL, metadata TEXT NOT NULL DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); ` _, err := db.Exec(stmt) return err } // SessionCreate genera un token aleatorio de 32 bytes (64 hex chars) e // inserta la sesion en la tabla sessions. Crea la tabla si no existe. // Retorna la Session completa lista para devolver al cliente. func SessionCreate(db *sql.DB, userID string, ttl time.Duration, metadata map[string]any) (Session, error) { var zero Session if db == nil { return zero, fmt.Errorf("session_create: db nil") } if userID == "" { return zero, fmt.Errorf("session_create: user_id vacio") } if ttl <= 0 { return zero, fmt.Errorf("session_create: ttl debe ser positivo") } if err := sessionEnsureTable(db); err != nil { return zero, fmt.Errorf("session_create: crear tabla: %w", err) } raw := make([]byte, 32) if _, err := rand.Read(raw); err != nil { return zero, fmt.Errorf("session_create: rand: %w", err) } token := hex.EncodeToString(raw) now := time.Now().Unix() exp := now + int64(ttl.Seconds()) var metaJSON []byte if metadata == nil { metaJSON = []byte("{}") } else { b, err := json.Marshal(metadata) if err != nil { return zero, fmt.Errorf("session_create: marshal metadata: %w", err) } metaJSON = b } _, err := db.Exec( "INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)", token, userID, exp, now, string(metaJSON), ) if err != nil { return zero, fmt.Errorf("session_create: insert: %w", err) } return Session{ Token: token, UserID: userID, ExpiresAt: exp, CreatedAt: now, Metadata: metadata, }, nil }