buhapp-backend/internal/users/repo.go
ga c6d219c51f Telegram WebApp support + consent endpoint
- Telegram bot (long polling)
- /api/v1/auth/telegram (validate initData, auto-create user)
- /api/v1/auth/consents (record acceptances)
- users.telegram_id column, GetByTelegramID/CreateWithTelegramID
- ValidateInitData (HMAC-SHA256 with WebAppData secret)
2026-08-20 17:43:20 +00:00

153 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package users
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
type User struct {
ID uuid.UUID `json:"id"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
TelegramID *int64 `json:"telegram_id,omitempty"`
Name string `json:"name"`
Birthdate *time.Time `json:"birthdate,omitempty"`
Gender string `json:"gender"`
City string `json:"city"`
Bio string `json:"bio"`
PhotoURL string `json:"photo_url,omitempty"`
IsVerified bool `json:"is_verified"`
IsBlocked bool `json:"is_blocked"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastSeenAt *time.Time `json:"last_seen_at,omitempty"`
}
type Repo struct {
pool *pgxpool.Pool
}
func NewRepo(pool *pgxpool.Pool) *Repo {
return &Repo{pool: pool}
}
func (r *Repo) CreateWithPassword(ctx context.Context, u *User, passwordHash string) error {
return r.pool.QueryRow(ctx, `
INSERT INTO users (email, phone, password_hash, name, birthdate, gender, city)
VALUES (NULLIF($1, ''), NULLIF($2, ''), $3, $4, $5, NULLIF($6, ''), NULLIF($7, ''))
RETURNING id, created_at, updated_at`,
u.Email, u.Phone, passwordHash, u.Name, u.Birthdate, u.Gender, u.City,
).Scan(&u.ID, &u.CreatedAt, &u.UpdatedAt)
}
func (r *Repo) UpdateRaw(ctx context.Context, sql string, args ...interface{}) (int64, error) {
tag, err := r.pool.Exec(ctx, sql, args...)
if err != nil {
return 0, err
}
return tag.RowsAffected(), nil
}
// CreateWithTelegramID — создать пользователя с привязкой к Telegram
func (r *Repo) CreateWithTelegramID(ctx context.Context, u *User, tgID int64) error {
return r.pool.QueryRow(ctx, `
INSERT INTO users (id, telegram_id, name, password_hash)
VALUES ($1, $2, $3, '')
ON CONFLICT (telegram_id) DO UPDATE
SET name = EXCLUDED.name, updated_at = NOW()
RETURNING id, created_at, updated_at`,
u.ID, tgID, u.Name,
).Scan(&u.ID, &u.CreatedAt, &u.UpdatedAt)
}
func (r *Repo) GetByTelegramID(ctx context.Context, tgID int64) (*User, error) {
u := &User{}
var email, phone, gender, city, bio, photo *string
var birth *time.Time
var telegramID *int64
err := r.pool.QueryRow(ctx, `
SELECT id, email, phone, telegram_id, name, birthdate, gender, city, bio, photo_url,
is_verified, is_blocked, created_at, updated_at, last_seen_at
FROM users WHERE telegram_id=$1`, tgID,
).Scan(&u.ID, &email, &phone, &telegramID, &u.Name, &birth, &gender, &city, &bio, &photo,
&u.IsVerified, &u.IsBlocked, &u.CreatedAt, &u.UpdatedAt, &u.LastSeenAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, err
}
if email != nil { u.Email = *email }
if phone != nil { u.Phone = *phone }
if telegramID != nil { u.TelegramID = telegramID }
if gender != nil { u.Gender = *gender }
if city != nil { u.City = *city }
if bio != nil { u.Bio = *bio }
if photo != nil { u.PhotoURL = *photo }
if birth != nil { u.Birthdate = birth }
return u, nil
}
func (r *Repo) TouchLastSeen(ctx context.Context, id uuid.UUID) error {
_, err := r.pool.Exec(ctx, `UPDATE users SET last_seen_at=NOW() WHERE id=$1`, id)
return err
}
func (r *Repo) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
u := &User{}
var email, phone, gender, city, bio, photo *string
var birth *time.Time
err := r.pool.QueryRow(ctx, `
SELECT id, email, phone, name, birthdate, gender, city, bio, photo_url,
is_verified, is_blocked, created_at, updated_at, last_seen_at
FROM users WHERE id=$1`, id,
).Scan(&u.ID, &email, &phone, &u.Name, &birth, &gender, &city, &bio, &photo,
&u.IsVerified, &u.IsBlocked, &u.CreatedAt, &u.UpdatedAt, &u.LastSeenAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, err
}
if email != nil { u.Email = *email }
if phone != nil { u.Phone = *phone }
if gender != nil { u.Gender = *gender }
if city != nil { u.City = *city }
if bio != nil { u.Bio = *bio }
if photo != nil { u.PhotoURL = *photo }
if birth != nil { u.Birthdate = birth }
return u, nil
}
func (r *Repo) GetByEmail(ctx context.Context, email string) (*User, string, error) {
u := &User{}
var hash string
var em *string
var birth *time.Time
var gender, city, bio, photo *string
err := r.pool.QueryRow(ctx, `
SELECT id, email, password_hash, name, birthdate, gender, city, bio, photo_url,
is_verified, is_blocked, created_at, updated_at, last_seen_at
FROM users WHERE email=$1`, email,
).Scan(&u.ID, &em, &hash, &u.Name, &birth, &gender, &city, &bio, &photo,
&u.IsVerified, &u.IsBlocked, &u.CreatedAt, &u.UpdatedAt, &u.LastSeenAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
if em != nil { u.Email = *em }
if gender != nil { u.Gender = *gender }
if city != nil { u.City = *city }
if bio != nil { u.Bio = *bio }
if photo != nil { u.PhotoURL = *photo }
if birth != nil { u.Birthdate = birth }
return u, hash, nil
}