102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"time"
|
|
|
|
"git.noroutes.net/NoRoutes/vibetunes/internal/idgen"
|
|
)
|
|
|
|
type APIKey struct {
|
|
ID string
|
|
UserID string
|
|
Name string
|
|
KeyHash string
|
|
CreatedAt time.Time
|
|
LastUsedAt sql.NullTime
|
|
RevokedAt sql.NullTime
|
|
}
|
|
|
|
func CreateAPIKey(db *sql.DB, userID, name, keyHash string) (*APIKey, error) {
|
|
id := idgen.New("ak")
|
|
_, err := db.Exec(`INSERT INTO api_keys (id, user_id, name, key_hash) VALUES (?, ?, ?, ?)`, id, userID, name, keyHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return GetAPIKey(db, id)
|
|
}
|
|
|
|
func GetAPIKey(db *sql.DB, id string) (*APIKey, error) {
|
|
return scanAPIKey(db.QueryRow(`SELECT id, user_id, name, key_hash, created_at, last_used_at, revoked_at FROM api_keys WHERE id = ?`, id))
|
|
}
|
|
|
|
func GetAPIKeyByHash(db *sql.DB, keyHash string) (*APIKey, error) {
|
|
return scanAPIKey(db.QueryRow(`SELECT id, user_id, name, key_hash, created_at, last_used_at, revoked_at FROM api_keys WHERE key_hash = ? AND revoked_at IS NULL`, keyHash))
|
|
}
|
|
|
|
func scanAPIKey(row *sql.Row) (*APIKey, error) {
|
|
var k APIKey
|
|
var createdAt string
|
|
var lastUsed, revoked sql.NullString
|
|
err := row.Scan(&k.ID, &k.UserID, &k.Name, &k.KeyHash, &createdAt, &lastUsed, &revoked)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
k.CreatedAt, _ = parseTime(createdAt)
|
|
if lastUsed.Valid {
|
|
if t, err := parseTime(lastUsed.String); err == nil {
|
|
k.LastUsedAt = sql.NullTime{Time: t, Valid: true}
|
|
}
|
|
}
|
|
if revoked.Valid {
|
|
if t, err := parseTime(revoked.String); err == nil {
|
|
k.RevokedAt = sql.NullTime{Time: t, Valid: true}
|
|
}
|
|
}
|
|
return &k, nil
|
|
}
|
|
|
|
func ListAPIKeysForUser(db *sql.DB, userID string) ([]*APIKey, error) {
|
|
rows, err := db.Query(`SELECT id, user_id, name, key_hash, created_at, last_used_at, revoked_at FROM api_keys WHERE user_id = ? ORDER BY created_at DESC`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []*APIKey
|
|
for rows.Next() {
|
|
var k APIKey
|
|
var createdAt string
|
|
var lastUsed, revoked sql.NullString
|
|
if err := rows.Scan(&k.ID, &k.UserID, &k.Name, &k.KeyHash, &createdAt, &lastUsed, &revoked); err != nil {
|
|
return nil, err
|
|
}
|
|
k.CreatedAt, _ = parseTime(createdAt)
|
|
if lastUsed.Valid {
|
|
if t, err := parseTime(lastUsed.String); err == nil {
|
|
k.LastUsedAt = sql.NullTime{Time: t, Valid: true}
|
|
}
|
|
}
|
|
if revoked.Valid {
|
|
if t, err := parseTime(revoked.String); err == nil {
|
|
k.RevokedAt = sql.NullTime{Time: t, Valid: true}
|
|
}
|
|
}
|
|
out = append(out, &k)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func TouchAPIKey(db *sql.DB, id string) error {
|
|
_, err := db.Exec(`UPDATE api_keys SET last_used_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id = ?`, id)
|
|
return err
|
|
}
|
|
|
|
func RevokeAPIKey(db *sql.DB, id string) error {
|
|
_, err := db.Exec(`UPDATE api_keys SET revoked_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id = ?`, id)
|
|
return err
|
|
}
|