mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
225 lines
6.0 KiB
Go
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
|
|
}
|