Merge pull request 'feat: 添加 PostgreSQL 服务并挂载数据库迁移脚本' #101
101
backend/internal/store/user_pg.go
Normal file
101
backend/internal/store/user_pg.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PgUserRepository 基于 PostgreSQL 的 UserRepository 实现。
|
||||||
|
type PgUserRepository struct {
|
||||||
|
pool *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPgUserRepository 创建 PgUserRepository。
|
||||||
|
func NewPgUserRepository(pool *pgxpool.Pool) *PgUserRepository {
|
||||||
|
return &PgUserRepository{pool: pool}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) Create(ctx context.Context, username, passwordHash string) (string, error) {
|
||||||
|
var id string
|
||||||
|
err := r.pool.QueryRow(ctx,
|
||||||
|
`INSERT INTO users (username, password_hash) VALUES ($1, $2) RETURNING id`,
|
||||||
|
username, passwordHash,
|
||||||
|
).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) FindByUsername(ctx context.Context, username string) (*User, error) {
|
||||||
|
var u User
|
||||||
|
err := r.pool.QueryRow(ctx,
|
||||||
|
`SELECT id, username, password_hash, created_at, updated_at FROM users WHERE username = $1`,
|
||||||
|
username,
|
||||||
|
).Scan(&u.ID, &u.Username, &u.PasswordHash, &u.CreatedAt, &u.UpdatedAt)
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, ErrUserNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
|
||||||
|
var u User
|
||||||
|
err := r.pool.QueryRow(ctx,
|
||||||
|
`SELECT id, username, password_hash, created_at, updated_at FROM users WHERE id = $1`,
|
||||||
|
id,
|
||||||
|
).Scan(&u.ID, &u.Username, &u.PasswordHash, &u.CreatedAt, &u.UpdatedAt)
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, ErrUserNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) SaveRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error {
|
||||||
|
_, err := r.pool.Exec(ctx,
|
||||||
|
`INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3)`,
|
||||||
|
userID, tokenHash, expiresAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) FindRefreshToken(ctx context.Context, tokenHash string) (string, error) {
|
||||||
|
var userID string
|
||||||
|
err := r.pool.QueryRow(ctx,
|
||||||
|
`SELECT user_id FROM refresh_tokens WHERE token_hash = $1 AND expires_at > NOW()`,
|
||||||
|
tokenHash,
|
||||||
|
).Scan(&userID)
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return "", ErrRefreshTokenNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) DeleteRefreshToken(ctx context.Context, tokenHash string) error {
|
||||||
|
_, err := r.pool.Exec(ctx,
|
||||||
|
`DELETE FROM refresh_tokens WHERE token_hash = $1`,
|
||||||
|
tokenHash,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PgUserRepository) DeleteUserRefreshTokens(ctx context.Context, userID string) error {
|
||||||
|
_, err := r.pool.Exec(ctx,
|
||||||
|
`DELETE FROM refresh_tokens WHERE user_id = $1`,
|
||||||
|
userID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user