Files
Bookra/apps/auth-service/internal/db/repository.go
T
Tomas Dvorak 48c3e15a38 cleanup
2026-05-05 09:48:07 +02:00

225 lines
6.0 KiB
Go

package db
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
type User struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
Name *string `json:"name,omitempty"`
PasswordHash *string `json:"-"`
EmailVerified bool `json:"email_verified"`
Provider string `json:"provider"`
ProviderID *string `json:"provider_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
}
type MagicLink struct {
Token string `json:"token"`
UserID uuid.UUID `json:"user_id"`
Email string `json:"email"`
Used bool `json:"used"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
}
func (db *DB) GetUserByEmail(ctx context.Context, email string) (*User, error) {
var user User
var name, passwordHash, providerID *string
var lastLoginAt *time.Time
err := db.QueryRow(ctx, `
SELECT id, email, name, password_hash, email_verified, provider, provider_id, created_at, updated_at, last_login_at
FROM users
WHERE email = $1
`, email).Scan(
&user.ID, &user.Email, &name, &passwordHash,
&user.EmailVerified, &user.Provider, &providerID,
&user.CreatedAt, &user.UpdatedAt, &lastLoginAt,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
user.Name = name
user.PasswordHash = passwordHash
user.ProviderID = providerID
user.LastLoginAt = lastLoginAt
return &user, nil
}
func (db *DB) GetUserByID(ctx context.Context, id uuid.UUID) (*User, error) {
var user User
var name, passwordHash, providerID *string
var lastLoginAt *time.Time
err := db.QueryRow(ctx, `
SELECT id, email, name, password_hash, email_verified, provider, provider_id, created_at, updated_at, last_login_at
FROM users
WHERE id = $1
`, id).Scan(
&user.ID, &user.Email, &name, &passwordHash,
&user.EmailVerified, &user.Provider, &providerID,
&user.CreatedAt, &user.UpdatedAt, &lastLoginAt,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
user.Name = name
user.PasswordHash = passwordHash
user.ProviderID = providerID
user.LastLoginAt = lastLoginAt
return &user, nil
}
func (db *DB) GetUserByProviderID(ctx context.Context, provider, providerID string) (*User, error) {
var user User
var name, passwordHash *string
var lastLoginAt *time.Time
err := db.QueryRow(ctx, `
SELECT id, email, name, password_hash, email_verified, provider, provider_id, created_at, updated_at, last_login_at
FROM users
WHERE provider = $1 AND provider_id = $2
`, provider, providerID).Scan(
&user.ID, &user.Email, &name, &passwordHash,
&user.EmailVerified, &user.Provider, &user.ProviderID,
&user.CreatedAt, &user.UpdatedAt, &lastLoginAt,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
user.Name = name
user.PasswordHash = passwordHash
user.LastLoginAt = lastLoginAt
return &user, nil
}
func (db *DB) CreateUser(ctx context.Context, user *User) (*User, error) {
if user.ID == uuid.Nil {
user.ID = uuid.Must(uuid.NewV7())
}
now := time.Now()
user.CreatedAt = now
user.UpdatedAt = now
_, err := db.pool.Exec(ctx, `
INSERT INTO users (id, email, name, password_hash, email_verified, provider, provider_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
`, user.ID, user.Email, user.Name, user.PasswordHash, user.EmailVerified, user.Provider, user.ProviderID, now)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
return user, nil
}
func (db *DB) UpdateUser(ctx context.Context, user *User) error {
user.UpdatedAt = time.Now()
_, err := db.pool.Exec(ctx, `
UPDATE users
SET email = $2, name = $3, password_hash = $4, email_verified = $5,
provider = $6, provider_id = $7, updated_at = $8, last_login_at = $9
WHERE id = $1
`, user.ID, user.Email, user.Name, user.PasswordHash, user.EmailVerified,
user.Provider, user.ProviderID, user.UpdatedAt, user.LastLoginAt)
return err
}
func (db *DB) UpdateLastLogin(ctx context.Context, userID uuid.UUID) error {
_, err := db.pool.Exec(ctx, `
UPDATE users SET last_login_at = NOW(), updated_at = NOW() WHERE id = $1
`, userID)
return err
}
func (db *DB) CreateMagicLink(ctx context.Context, token string, email string, userID uuid.UUID, expiresAt time.Time) error {
_, err := db.pool.Exec(ctx, `
INSERT INTO magic_links (token, user_id, email, expires_at, created_at)
VALUES ($1, $2, $3, $4, NOW())
`, token, userID, email, expiresAt)
return err
}
func (db *DB) GetMagicLink(ctx context.Context, token string) (*MagicLink, error) {
var ml MagicLink
var userID uuid.UUID
err := db.QueryRow(ctx, `
SELECT token, user_id, email, used, expires_at, created_at
FROM magic_links
WHERE token = $1
`, token).Scan(&ml.Token, &userID, &ml.Email, &ml.Used, &ml.ExpiresAt, &ml.CreatedAt)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
ml.UserID = userID
return &ml, nil
}
func (db *DB) MarkMagicLinkUsed(ctx context.Context, token string) error {
_, err := db.pool.Exec(ctx, `UPDATE magic_links SET used = true WHERE token = $1`, token)
return err
}
func (db *DB) PutKV(ctx context.Context, key string, value any) error {
payload, err := json.Marshal(value)
if err != nil {
return fmt.Errorf("marshal kv value: %w", err)
}
_, err = db.pool.Exec(ctx, `
INSERT INTO stripe_kv (key, value, created_at, updated_at)
VALUES ($1, $2, NOW(), NOW())
ON CONFLICT (key) DO UPDATE
SET value = EXCLUDED.value, updated_at = NOW()
`, key, payload)
return err
}
func (db *DB) GetKV(ctx context.Context, key string, dest any) (bool, error) {
var payload []byte
err := db.QueryRow(ctx, `
SELECT value
FROM stripe_kv
WHERE key = $1
`, key).Scan(&payload)
if err == pgx.ErrNoRows {
return false, nil
}
if err != nil {
return false, err
}
if err := json.Unmarshal(payload, dest); err != nil {
return false, fmt.Errorf("unmarshal kv value: %w", err)
}
return true, nil
}