fix for phone generation on email signup

This commit is contained in:
onysd 2026-07-14 00:30:52 +03:00
parent cee960fea0
commit 3409f190b9
20 changed files with 457 additions and 54 deletions

View file

@ -75,8 +75,14 @@ func TestEmailSignupChangePhoneRoutesCodeToDecodedEmail(t *testing.T) {
if err != nil {
t.Fatalf("ChangePhone: %v", err)
}
if result.User.Phone != newPhone {
t.Fatalf("result.User.Phone = %q, want %q", result.User.Phone, newPhone)
// Rebinding to a new email assigns a fresh short "888" display number
// (see auth.Service.SignUp's identical treatment) rather than storing
// the long wire value directly; SignupEmail carries the association.
if result.User.Phone == newPhone || !domain.ValidPhone(result.User.Phone) || domain.IsEmailSignupPhone(result.User.Phone) {
t.Fatalf("result.User.Phone = %q, want a short all-digit 888 display number distinct from the wire value %q", result.User.Phone, newPhone)
}
if result.User.SignupEmail != "newmail@owpengram.local" {
t.Fatalf("result.User.SignupEmail = %q, want newmail@owpengram.local", result.User.SignupEmail)
}
}

View file

@ -81,6 +81,15 @@ func (s *Service) sendChangePhoneCodeByEmail(ctx context.Context, userID int64,
if !ok {
return "", domain.AuthCodeDelivery{}, domain.ErrPhoneNumberInvalid
}
// The wire value never equals any stored users.phone once assigned (see
// ChangePhone), so the generic ByPhone occupied check above is a no-op
// for this path; the real "is this email already someone else's
// account" guard is by SignupEmail instead.
if existing, found, err := s.users.ByEmail(ctx, email); err != nil {
return "", domain.AuthCodeDelivery{}, err
} else if found && existing.ID != userID {
return "", domain.AuthCodeDelivery{}, domain.ErrPhoneNumberOccupied
}
if s.loginEmailSender == nil {
return "", domain.AuthCodeDelivery{}, fmt.Errorf("email signup sender is not configured")
}
@ -162,7 +171,7 @@ func (s *Service) ChangePhone(ctx context.Context, userID int64, authKeyID, orig
if date == 0 {
date = int(time.Now().Unix())
}
result, err := s.phoneChanges.ChangePhone(ctx, domain.PhoneChangeRequest{
req := domain.PhoneChangeRequest{
UserID: userID,
Phone: phone,
Date: date,
@ -171,7 +180,31 @@ func (s *Service) ChangePhone(ctx context.Context, userID int64, authKeyID, orig
// echoes updateUserPhone back to the initiating device and suppresses the wrong session.
ExcludeAuthKeyID: originRawAuthKeyID,
ExcludeSessionID: sessionID,
})
}
// Rebinding to a different email: the account keeps its short "888"
// display number (or gets a fresh one), never the long wire value —
// same reasoning as SignUp. ByPhone above is a no-op for this case since
// the wire value never equals a stored users.phone; the real
// already-bound-elsewhere guard is by SignupEmail.
if s.emailSignupEnabled && domain.IsEmailSignupPhone(phone) {
email, ok := domain.DecodeEmailPhone(phone)
if !ok {
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberInvalid
}
email = domain.NormalizeEmailForPhone(email)
if existing, found, err := s.users.ByEmail(ctx, email); err != nil {
return domain.PhoneChangeResult{}, err
} else if found && existing.ID != userID {
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberOccupied
}
displayPhone, err := s.assignEmailSignupDisplayPhone(ctx)
if err != nil {
return domain.PhoneChangeResult{}, err
}
req.Phone = displayPhone
req.SignupEmail = email
}
result, err := s.phoneChanges.ChangePhone(ctx, req)
if err != nil {
return domain.PhoneChangeResult{}, err
}
@ -205,6 +238,30 @@ func (s *Service) phoneChangeCaller(ctx context.Context, userID int64, authKeyID
return u, nil
}
// maxEmailSignupPhoneAttempts bounds assignEmailSignupDisplayPhone's
// collision-retry loop so a store failure can't spin forever.
const maxEmailSignupPhoneAttempts = 20
// assignEmailSignupDisplayPhone generates a short "888" display number for
// an email-signup account rebinding to a new email (see
// domain.NewEmailSignupDisplayPhone / auth.Service's SignUp counterpart),
// re-rolling on the astronomically unlikely collision with an existing
// account's phone.
func (s *Service) assignEmailSignupDisplayPhone(ctx context.Context) (string, error) {
for i := 0; i < maxEmailSignupPhoneAttempts; i++ {
candidate, err := domain.NewEmailSignupDisplayPhone()
if err != nil {
return "", err
}
if _, found, err := s.users.ByPhone(ctx, candidate); err != nil {
return "", err
} else if !found {
return candidate, nil
}
}
return "", fmt.Errorf("assign email signup display phone: exhausted %d attempts", maxEmailSignupPhoneAttempts)
}
func phoneChangeHash() (string, error) {
var raw [8]byte
if _, err := rand.Read(raw[:]); err != nil {

View file

@ -47,14 +47,20 @@ func TestEmailSignupSendCodeRoutesFreshSignupToEmail(t *testing.T) {
t.Fatalf("needSignUp = false, want true for a brand-new email-signup account")
}
// SignUp itself is completely untouched by email-signup: same call, same
// phone (the 888-encoded value), no email-specific parameter anywhere.
// SignUp itself is called exactly like the plain-phone path (same phone
// argument, no email-specific parameter anywhere), but the account it
// creates gets a short, normal-looking "888" display number instead of
// storing the long email-encoded wire value as its permanent phone; the
// email->user association lives in SignupEmail instead.
created, _, err := svc.SignUp(ctx, domain.Authorization{}, phone, hash, "New", "User")
if err != nil {
t.Fatalf("SignUp: %v", err)
}
if created.Phone != phone {
t.Fatalf("created.Phone = %q, want %q", created.Phone, phone)
if created.Phone == phone || !domain.ValidPhone(created.Phone) || domain.IsEmailSignupPhone(created.Phone) {
t.Fatalf("created.Phone = %q, want a short all-digit 888 display number distinct from the wire value %q", created.Phone, phone)
}
if created.SignupEmail != "newuser@owpengram.local" {
t.Fatalf("created.SignupEmail = %q, want newuser@owpengram.local", created.SignupEmail)
}
}
@ -88,8 +94,11 @@ func TestEmailSignupSignUpSucceedsWithLoginEmailRequireSetupAlsoOn(t *testing.T)
if err != nil {
t.Fatalf("SignUp: %v (this is the loop bug if it fails with ErrCodeInvalid)", err)
}
if created.Phone != phone {
t.Fatalf("created.Phone = %q, want %q", created.Phone, phone)
if created.Phone == phone || !domain.ValidPhone(created.Phone) || domain.IsEmailSignupPhone(created.Phone) {
t.Fatalf("created.Phone = %q, want a short all-digit 888 display number distinct from the wire value %q", created.Phone, phone)
}
if created.SignupEmail != "requiresetup@owpengram.local" {
t.Fatalf("created.SignupEmail = %q, want requiresetup@owpengram.local", created.SignupEmail)
}
}

View file

@ -298,7 +298,7 @@ func (s *Service) CompletePasswordSignIn(ctx context.Context, authKeyID [8]byte)
// deliberately skipped the welcome message while password_pending.
if a, found, err := s.auths.ByAuthKey(ctx, authKeyID); err == nil && found {
if u, found, err := s.users.ByID(ctx, a.UserID); err == nil && found {
s.recordWelcomeMessage(ctx, u.ID, u.Phone)
s.recordWelcomeMessage(ctx, u)
}
}
return nil
@ -345,10 +345,25 @@ func (s *Service) SendCode(ctx context.Context, phone string) (string, error) {
return s.createPhoneCode(ctx, phone, issuedUserID)
}
// currentPhoneOwner resolves the account currently identified by a wire
// phone value. For email-signup phones (see domain.EncodeEmailPhone) the
// account's real users.phone is a short, unrelated "888" display number
// (domain.NewEmailSignupDisplayPhone) assigned at SignUp — the wire value
// itself is never stored — so lookup goes through the decoded email and
// User.SignupEmail instead of ByPhone. Every owner-drift/idempotency
// invariant elsewhere in this file (issuedOwnerMatches, verifyLoginCode,
// SignUp's occupied checks, ...) is expressed purely in terms of "the
// account currentPhoneOwner resolves to", so fixing this one lookup keeps
// all of them correct for email-signup phones with no further changes.
func (s *Service) currentPhoneOwner(ctx context.Context, phone string) (domain.User, bool, error) {
if s == nil || s.users == nil {
return domain.User{}, false, fmt.Errorf("user store is not configured")
}
if s.emailSignupEnabled {
if email, ok := domain.DecodeEmailPhone(phone); ok {
return s.users.ByEmail(ctx, email)
}
}
return s.users.ByPhone(ctx, phone)
}
@ -906,7 +921,7 @@ func (s *Service) finishSignIn(ctx context.Context, auth domain.Authorization, e
// 2FA accounts only really finish authorizing in CompletePasswordSignIn;
// firing the welcome message here too would notify about an attempt that
// never actually got past the password check.
s.recordWelcomeMessage(ctx, existing.ID, existing.Phone)
s.recordWelcomeMessage(ctx, existing)
return existing, domain.Message{}, false, nil
}
@ -993,6 +1008,23 @@ func (s *Service) SignUp(ctx context.Context, auth domain.Authorization, phone,
FirstName: firstName,
LastName: lastName,
}
// Email-signup accounts don't keep the long email-encoded wire value as
// their permanent phone — that only ever needed to travel on the wire to
// get here. Assign a short, normal-looking "888" number instead and
// record the email separately (SignupEmail), which is what
// currentPhoneOwner uses to find this account again on a later login.
if s.emailSignupEnabled && domain.IsEmailSignupPhone(phone) {
email, ok := domain.DecodeEmailPhone(phone)
if !ok {
return domain.User{}, domain.Message{}, ErrPhoneNumberInvalid
}
displayPhone, err := s.assignEmailSignupDisplayPhone(ctx)
if err != nil {
return domain.User{}, domain.Message{}, err
}
newUser.Phone = displayPhone
newUser.SignupEmail = domain.NormalizeEmailForPhone(email)
}
// 新账号默认赠送会员:到期时间 = 注册时刻 + N 个月(与迁移 0094 对存量
// 账号的 backfill 同一语义。premium 状态由下发路径按该时间即时派生。
if s.premiumGrantMonths > 0 {
@ -1021,7 +1053,7 @@ func (s *Service) SignUp(ctx context.Context, auth domain.Authorization, phone,
return domain.User{}, domain.Message{}, err
}
}
s.recordWelcomeMessage(ctx, u.ID, phone)
s.recordWelcomeMessage(ctx, u)
return u, loginMessage, nil
}
@ -1329,11 +1361,11 @@ func (s *Service) recordLoginMessage(ctx context.Context, userID int64, code str
// SignIn/SignInWithEmail), regardless of channel. Best-effort: a failure here
// must never fail the sign-in itself, since unlike recordLoginMessage it
// carries no secret the caller needs.
func (s *Service) recordWelcomeMessage(ctx context.Context, userID int64, phone string) {
func (s *Service) recordWelcomeMessage(ctx context.Context, u domain.User) {
if s == nil || s.messages == nil || s.dialogs == nil {
return
}
msg, err := domain.OfficialWelcomeMessage(userID, domain.SignInMethodLabel(phone), int(time.Now().Unix()))
msg, err := domain.OfficialWelcomeMessage(u.ID, domain.SignInMethodLabel(u), int(time.Now().Unix()))
if err != nil {
return
}
@ -1341,7 +1373,7 @@ func (s *Service) recordWelcomeMessage(ctx context.Context, userID int64, phone
if err != nil {
return
}
_ = s.dialogs.UpsertInbox(ctx, userID, domain.Dialog{
_ = s.dialogs.UpsertInbox(ctx, u.ID, domain.Dialog{
Peer: created.Peer,
TopMessage: created.ID,
TopMessageDate: created.Date,
@ -1445,6 +1477,28 @@ func normalizePhone(phone string) string {
return domain.NormalizePhone(phone)
}
// assignEmailSignupDisplayPhone generates a short "888" display number for a
// new email-signup account (see domain.NewEmailSignupDisplayPhone),
// re-rolling on the astronomically unlikely collision with an existing
// account's phone. maxEmailSignupPhoneAttempts bounds the loop so a store
// failure can't spin forever.
const maxEmailSignupPhoneAttempts = 20
func (s *Service) assignEmailSignupDisplayPhone(ctx context.Context) (string, error) {
for i := 0; i < maxEmailSignupPhoneAttempts; i++ {
candidate, err := domain.NewEmailSignupDisplayPhone()
if err != nil {
return "", err
}
if _, found, err := s.users.ByPhone(ctx, candidate); err != nil {
return "", err
} else if !found {
return candidate, nil
}
}
return "", fmt.Errorf("assign email signup display phone: exhausted %d attempts", maxEmailSignupPhoneAttempts)
}
func randomHex(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {

View file

@ -1,6 +1,10 @@
package domain
import "strings"
import (
"crypto/rand"
"fmt"
"strings"
)
// EmailPhonePrefix marks a "phone number" as a synthetic identity encoding an
// email address, not a real phone. It reuses Telegram's own +888 "Anonymous
@ -24,11 +28,11 @@ const MaxEmailSignupPhoneLen = 200
const emailPhoneEscape = 'q'
var emailPhoneEscapeEncode = map[rune]byte{
'@': '0',
'.': '1',
'-': '2',
'_': '3',
'+': '4',
'@': '0',
'.': '1',
'-': '2',
'_': '3',
'+': '4',
emailPhoneEscape: '5',
}
@ -135,6 +139,33 @@ func NormalizeEmailForPhone(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}
// emailSignupDisplayPhoneDigits is how many random digits follow the "888"
// prefix in a NewEmailSignupDisplayPhone result, e.g. "888" + 8 digits =
// "88812345678" (formats on-screen as something like "+888 1234 5678").
const emailSignupDisplayPhoneDigits = 8
// NewEmailSignupDisplayPhone generates a short, all-digit "888" phone number
// for an email-signup account's users.phone column. Unlike EncodeEmailPhone
// this carries no information about the email — it is purely a
// normal-looking display/identity number — so the caller must separately
// persist the email->user association (see User.SignupEmail) for returning
// logins to be found. Because the result is all digits, IsEmailSignupPhone
// on it is always false: once assigned, it behaves exactly like a real phone
// number everywhere else in the system (contacts, search, ByPhone lookups).
func NewEmailSignupDisplayPhone() (string, error) {
b := make([]byte, emailSignupDisplayPhoneDigits)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("generate email signup display phone: %w", err)
}
var out strings.Builder
out.Grow(len(EmailPhonePrefix) + emailSignupDisplayPhoneDigits)
out.WriteString(EmailPhonePrefix)
for _, v := range b {
out.WriteByte('0' + v%10)
}
return out.String(), nil
}
// IsEmailSignupPhone reports whether phone was produced by EncodeEmailPhone.
// Every encoded value contains at least one letter (the mandatory '@'
// escape's 'q' marker byte), which real, all-digit phone numbers — even

View file

@ -1,6 +1,9 @@
package domain
import "testing"
import (
"strings"
"testing"
)
func TestEncodeDecodeEmailPhoneRoundTrip(t *testing.T) {
for _, email := range []string{
@ -62,3 +65,32 @@ func TestDecodeEmailPhoneRejectsNonEmailNumbers(t *testing.T) {
}
}
}
func TestNewEmailSignupDisplayPhoneLooksLikeARealPhoneNumber(t *testing.T) {
seen := make(map[string]struct{})
for i := 0; i < 200; i++ {
phone, err := NewEmailSignupDisplayPhone()
if err != nil {
t.Fatalf("NewEmailSignupDisplayPhone: %v", err)
}
if !strings.HasPrefix(phone, EmailPhonePrefix) {
t.Fatalf("phone %q missing %q prefix", phone, EmailPhonePrefix)
}
if !ValidPhone(phone) {
t.Fatalf("phone %q fails ValidPhone", phone)
}
// Must be indistinguishable from a real phone number: no letters, so
// IsEmailSignupPhone/DecodeEmailPhone never mistake it for a wire
// email-signup value once it's assigned as an account's real phone.
if IsEmailSignupPhone(phone) {
t.Fatalf("IsEmailSignupPhone(%q) = true, want false (must look like a real number)", phone)
}
if _, ok := DecodeEmailPhone(phone); ok {
t.Fatalf("DecodeEmailPhone(%q) unexpectedly succeeded", phone)
}
seen[phone] = struct{}{}
}
if len(seen) < 190 {
t.Fatalf("only %d distinct values out of 200 draws, generator looks non-random", len(seen))
}
}

View file

@ -9,6 +9,11 @@ type PhoneChangeRequest struct {
Date int
ExcludeAuthKeyID [8]byte
ExcludeSessionID int64
// SignupEmail, when non-empty, is written to users.signup_email in the
// same transaction as Phone. Only email-signup phone changes set this
// (see account.Service.ChangePhone); ordinary phone-number changes leave
// it as the zero value and the column untouched.
SignupEmail string
}
type PhoneChangeResult struct {

View file

@ -22,9 +22,14 @@ func (c PeerColor) Empty() bool {
// User 是一个账号。第一阶段仅保留登录链路必须字段;
// access_hash 为任何 InputUser 校验所必须,不可省。
type User struct {
ID int64
AccessHash int64
Phone string
ID int64
AccessHash int64
Phone string
// SignupEmail is set only for email-signup accounts (see
// domain.NewEmailSignupDisplayPhone): it is the durable email->user
// reverse lookup key, since Phone itself no longer encodes the email.
// Empty for every ordinary phone-number account.
SignupEmail string
FirstName string
LastName string
About string

View file

@ -30,10 +30,12 @@ func OfficialWelcomeMessage(userID int64, method string, date int) (Message, err
}
// SignInMethodLabel returns the human-readable method name embedded in
// OfficialWelcomeMessage, derived from whether phone is an email-signup
// synthetic number (see EncodeEmailPhone) or a real phone number.
func SignInMethodLabel(phone string) string {
if IsEmailSignupPhone(phone) {
// OfficialWelcomeMessage. Email-signup accounts are identified by
// SignupEmail (see NewEmailSignupDisplayPhone) rather than by their stored
// Phone, which — once assigned — is an ordinary-looking short number that
// carries no information about the signup method.
func SignInMethodLabel(u User) string {
if u.SignupEmail != "" {
return "email"
}
return "phone number"

View file

@ -42,7 +42,11 @@ func (s *PhoneChangeStore) ChangePhone(ctx context.Context, req domain.PhoneChan
}
}
currentPhone := u.Phone
currentSignupEmail := u.SignupEmail
u.Phone = req.Phone
if req.SignupEmail != "" {
u.SignupEmail = req.SignupEmail
}
s.users.byID[req.UserID] = u
date := req.Date
@ -62,6 +66,7 @@ func (s *PhoneChangeStore) ChangePhone(ctx context.Context, req domain.PhoneChan
if err != nil {
// 保持内存替身与 PG 的 user+event 原子可见语义。
u.Phone = currentPhone
u.SignupEmail = currentSignupEmail
s.users.byID[req.UserID] = u
s.users.mu.Unlock()
return domain.PhoneChangeResult{}, err

View file

@ -2,6 +2,7 @@ package memory
import (
"context"
"fmt"
"sort"
"strings"
"sync"
@ -72,6 +73,23 @@ func (s *UserStore) ByPhone(_ context.Context, phone string) (domain.User, bool,
return domain.User{}, false, nil
}
// ByEmail looks up an email-signup account by its signup_email (see
// domain.NewEmailSignupDisplayPhone). Mirrors postgres.UserStore.ByEmail.
func (s *UserStore) ByEmail(_ context.Context, email string) (domain.User, bool, error) {
email = strings.ToLower(strings.TrimSpace(email))
if email == "" {
return domain.User{}, false, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
for _, u := range s.byID {
if u.SignupEmail != "" && strings.ToLower(u.SignupEmail) == email {
return u, true, nil
}
}
return domain.User{}, false, nil
}
func (s *UserStore) ByPhones(_ context.Context, phones []string) ([]domain.User, error) {
if len(phones) == 0 {
return nil, nil
@ -377,6 +395,14 @@ func (s *UserStore) Create(_ context.Context, u domain.User) (domain.User, error
}
}
}
signupEmail := strings.ToLower(strings.TrimSpace(u.SignupEmail))
if signupEmail != "" {
for _, existing := range s.byID {
if existing.SignupEmail != "" && strings.ToLower(existing.SignupEmail) == signupEmail {
return domain.User{}, fmt.Errorf("create user: signup email occupied")
}
}
}
u.ID = s.nextID
s.nextID++
s.byID[u.ID] = u

View file

@ -64,9 +64,14 @@ func (s *PhoneChangeStore) ChangePhone(ctx context.Context, req domain.PhoneChan
return domain.PhoneChangeResult{User: userFromModel(row)}, nil
}
row, err := qtx.UpdateUserPhone(ctx, sqlcgen.UpdateUserPhoneParams{ID: req.UserID, Phone: req.Phone})
var row sqlcgen.User
if req.SignupEmail != "" {
row, err = qtx.UpdateUserPhoneAndSignupEmail(ctx, sqlcgen.UpdateUserPhoneAndSignupEmailParams{ID: req.UserID, Phone: req.Phone, SignupEmail: req.SignupEmail})
} else {
row, err = qtx.UpdateUserPhone(ctx, sqlcgen.UpdateUserPhoneParams{ID: req.UserID, Phone: req.Phone})
}
if err != nil {
if isUniqueConstraint(err, "users_phone_unique_idx") {
if isUniqueConstraint(err, "users_phone_unique_idx") || isUniqueConstraint(err, "users_signup_email_lower_unique_idx") {
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberOccupied
}
if errors.Is(err, pgx.ErrNoRows) {

View file

@ -10,6 +10,9 @@ ORDER BY id;
-- name: GetUserByPhone :one
SELECT * FROM users WHERE phone = $1;
-- name: GetUserBySignupEmail :one
SELECT * FROM users WHERE lower(signup_email) = lower($1) AND signup_email <> '';
-- name: GetUsersByPhones :many
SELECT *
FROM users
@ -99,8 +102,8 @@ ORDER BY contact DESC, rank, id
LIMIT sqlc.arg(limit_count);
-- name: CreateUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code, premium_expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
INSERT INTO users (access_hash, phone, signup_email, first_name, last_name, username, country_code, premium_expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *;
-- name: UpdateUserUsername :one
@ -132,6 +135,14 @@ SET phone = sqlc.arg(phone)::text,
WHERE id = sqlc.arg(id)::bigint
RETURNING *;
-- name: UpdateUserPhoneAndSignupEmail :one
UPDATE users
SET phone = sqlc.arg(phone)::text,
signup_email = sqlc.arg(signup_email)::text,
updated_at = now()
WHERE id = sqlc.arg(id)::bigint
RETURNING *;
-- name: SetUserPremiumUntil :one
UPDATE users
SET premium_expires_at = sqlc.narg(premium_expires_at)::timestamptz,

View file

@ -167,7 +167,7 @@ func (q *Queries) InsertBot(ctx context.Context, arg InsertBotParams) error {
const insertBotUser = `-- name: InsertBotUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code, is_bot, bot_info_version)
VALUES ($1, '', $2, '', $3, '', TRUE, 1)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type InsertBotUserParams struct {
@ -209,6 +209,7 @@ func (q *Queries) InsertBotUser(ctx context.Context, arg InsertBotUserParams) (U
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}

View file

@ -1579,6 +1579,7 @@ type User struct {
BirthdayMonth int32
BirthdayYear int32
PersonalChannelID int64
SignupEmail string
}
type UserBusinessProfile struct {

View file

@ -12,14 +12,15 @@ import (
)
const createUser = `-- name: CreateUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code, premium_expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
INSERT INTO users (access_hash, phone, signup_email, first_name, last_name, username, country_code, premium_expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type CreateUserParams struct {
AccessHash int64
Phone string
SignupEmail string
FirstName string
LastName string
Username string
@ -31,6 +32,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
row := q.db.QueryRow(ctx, createUser,
arg.AccessHash,
arg.Phone,
arg.SignupEmail,
arg.FirstName,
arg.LastName,
arg.Username,
@ -68,12 +70,13 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const getUserByID = `-- name: GetUserByID :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id FROM users WHERE id = $1
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email FROM users WHERE id = $1
`
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
@ -109,12 +112,13 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const getUserByPhone = `-- name: GetUserByPhone :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id FROM users WHERE phone = $1
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email FROM users WHERE phone = $1
`
func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error) {
@ -150,12 +154,55 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const getUserBySignupEmail = `-- name: GetUserBySignupEmail :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email FROM users WHERE lower(signup_email) = lower($1) AND signup_email <> ''
`
func (q *Queries) GetUserBySignupEmail(ctx context.Context, lower string) (User, error) {
row := q.db.QueryRow(ctx, getUserBySignupEmail, lower)
var i User
err := row.Scan(
&i.ID,
&i.AccessHash,
&i.Phone,
&i.FirstName,
&i.LastName,
&i.Username,
&i.CountryCode,
&i.CreatedAt,
&i.UpdatedAt,
&i.Verified,
&i.Support,
&i.About,
&i.LastSeenAt,
&i.DefaultHistoryTtlPeriod,
&i.IsBot,
&i.BotInfoVersion,
&i.PremiumExpiresAt,
&i.EmojiStatusDocumentID,
&i.EmojiStatusUntil,
&i.ColorSet,
&i.Color,
&i.ColorBackgroundEmojiID,
&i.ProfileColorSet,
&i.ProfileColor,
&i.ProfileColorBackgroundEmojiID,
&i.BirthdayDay,
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const getUserByUsername = `-- name: GetUserByUsername :one
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id FROM users WHERE lower(username) = lower($1) AND username <> ''
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email FROM users WHERE lower(username) = lower($1) AND username <> ''
`
func (q *Queries) GetUserByUsername(ctx context.Context, lower string) (User, error) {
@ -191,12 +238,13 @@ func (q *Queries) GetUserByUsername(ctx context.Context, lower string) (User, er
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const getUsersByIDs = `-- name: GetUsersByIDs :many
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
FROM users
WHERE id = ANY($1::bigint[])
ORDER BY id
@ -241,6 +289,7 @@ func (q *Queries) GetUsersByIDs(ctx context.Context, ids []int64) ([]User, error
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
); err != nil {
return nil, err
}
@ -253,7 +302,7 @@ func (q *Queries) GetUsersByIDs(ctx context.Context, ids []int64) ([]User, error
}
const getUsersByPhones = `-- name: GetUsersByPhones :many
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
SELECT id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
FROM users
WHERE phone = ANY($1::text[])
ORDER BY id
@ -298,6 +347,7 @@ func (q *Queries) GetUsersByPhones(ctx context.Context, phones []string) ([]User
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
); err != nil {
return nil, err
}
@ -480,7 +530,7 @@ UPDATE users
SET premium_expires_at = $1::timestamptz,
updated_at = now()
WHERE id = $2::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type SetUserPremiumUntilParams struct {
@ -521,6 +571,7 @@ func (q *Queries) SetUserPremiumUntil(ctx context.Context, arg SetUserPremiumUnt
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -530,7 +581,7 @@ UPDATE users
SET verified = $1::boolean,
updated_at = now()
WHERE id = $2::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type SetUserVerifiedParams struct {
@ -571,6 +622,7 @@ func (q *Queries) SetUserVerified(ctx context.Context, arg SetUserVerifiedParams
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -586,7 +638,7 @@ WHERE id IN (
ORDER BY premium_expires_at
LIMIT $2::int
)
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type SweepExpiredPremiumParams struct {
@ -633,6 +685,7 @@ func (q *Queries) SweepExpiredPremium(ctx context.Context, arg SweepExpiredPremi
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
); err != nil {
return nil, err
}
@ -651,7 +704,7 @@ SET birthday_day = $1::int,
birthday_year = $3::int,
updated_at = now()
WHERE id = $4::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserBirthdayParams struct {
@ -699,6 +752,7 @@ func (q *Queries) UpdateUserBirthday(ctx context.Context, arg UpdateUserBirthday
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -710,7 +764,7 @@ SET color_set = $1::boolean,
color_background_emoji_id = $3::bigint,
updated_at = now()
WHERE id = $4::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserColorParams struct {
@ -758,6 +812,7 @@ func (q *Queries) UpdateUserColor(ctx context.Context, arg UpdateUserColorParams
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -768,7 +823,7 @@ SET emoji_status_document_id = $1::bigint,
emoji_status_until = $2::bigint,
updated_at = now()
WHERE id = $3::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserEmojiStatusParams struct {
@ -810,6 +865,7 @@ func (q *Queries) UpdateUserEmojiStatus(ctx context.Context, arg UpdateUserEmoji
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -836,7 +892,7 @@ UPDATE users
SET personal_channel_id = $1::bigint,
updated_at = now()
WHERE id = $2::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserPersonalChannelParams struct {
@ -877,6 +933,7 @@ func (q *Queries) UpdateUserPersonalChannel(ctx context.Context, arg UpdateUserP
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -886,7 +943,7 @@ UPDATE users
SET phone = $1::text,
updated_at = now()
WHERE id = $2::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserPhoneParams struct {
@ -927,6 +984,60 @@ func (q *Queries) UpdateUserPhone(ctx context.Context, arg UpdateUserPhoneParams
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
const updateUserPhoneAndSignupEmail = `-- name: UpdateUserPhoneAndSignupEmail :one
UPDATE users
SET phone = $1::text,
signup_email = $2::text,
updated_at = now()
WHERE id = $3::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserPhoneAndSignupEmailParams struct {
Phone string
SignupEmail string
ID int64
}
func (q *Queries) UpdateUserPhoneAndSignupEmail(ctx context.Context, arg UpdateUserPhoneAndSignupEmailParams) (User, error) {
row := q.db.QueryRow(ctx, updateUserPhoneAndSignupEmail, arg.Phone, arg.SignupEmail, arg.ID)
var i User
err := row.Scan(
&i.ID,
&i.AccessHash,
&i.Phone,
&i.FirstName,
&i.LastName,
&i.Username,
&i.CountryCode,
&i.CreatedAt,
&i.UpdatedAt,
&i.Verified,
&i.Support,
&i.About,
&i.LastSeenAt,
&i.DefaultHistoryTtlPeriod,
&i.IsBot,
&i.BotInfoVersion,
&i.PremiumExpiresAt,
&i.EmojiStatusDocumentID,
&i.EmojiStatusUntil,
&i.ColorSet,
&i.Color,
&i.ColorBackgroundEmojiID,
&i.ProfileColorSet,
&i.ProfileColor,
&i.ProfileColorBackgroundEmojiID,
&i.BirthdayDay,
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -938,7 +1049,7 @@ SET first_name = $2,
about = $4,
updated_at = now()
WHERE id = $1
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserProfileParams struct {
@ -986,6 +1097,7 @@ func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfilePa
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -997,7 +1109,7 @@ SET profile_color_set = $1::boolean,
profile_color_background_emoji_id = $3::bigint,
updated_at = now()
WHERE id = $4::bigint
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserProfileColorParams struct {
@ -1045,6 +1157,7 @@ func (q *Queries) UpdateUserProfileColor(ctx context.Context, arg UpdateUserProf
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}
@ -1054,7 +1167,7 @@ UPDATE users
SET username = $2,
updated_at = now()
WHERE id = $1
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id
RETURNING id, access_hash, phone, first_name, last_name, username, country_code, created_at, updated_at, verified, support, about, last_seen_at, default_history_ttl_period, is_bot, bot_info_version, premium_expires_at, emoji_status_document_id, emoji_status_until, color_set, color, color_background_emoji_id, profile_color_set, profile_color, profile_color_background_emoji_id, birthday_day, birthday_month, birthday_year, personal_channel_id, signup_email
`
type UpdateUserUsernameParams struct {
@ -1095,6 +1208,7 @@ func (q *Queries) UpdateUserUsername(ctx context.Context, arg UpdateUserUsername
&i.BirthdayMonth,
&i.BirthdayYear,
&i.PersonalChannelID,
&i.SignupEmail,
)
return i, err
}

View file

@ -66,6 +66,24 @@ func (s *UserStore) ByPhone(ctx context.Context, phone string) (domain.User, boo
return userFromModel(row), true, nil
}
// ByEmail looks up an email-signup account by its signup_email (see
// domain.NewEmailSignupDisplayPhone). Ordinary phone accounts never match
// since signup_email is '' for them and the index excludes empty values.
func (s *UserStore) ByEmail(ctx context.Context, email string) (domain.User, bool, error) {
email = strings.TrimSpace(email)
if email == "" {
return domain.User{}, false, nil
}
row, err := s.q.GetUserBySignupEmail(ctx, email)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.User{}, false, nil
}
return domain.User{}, false, fmt.Errorf("get user by signup email: %w", err)
}
return userFromModel(row), true, nil
}
func (s *UserStore) ByPhones(ctx context.Context, phones []string) ([]domain.User, error) {
filtered := make([]string, 0, len(phones))
for _, phone := range phones {
@ -262,6 +280,7 @@ func (s *UserStore) Create(ctx context.Context, u domain.User) (domain.User, err
row, err := qtx.CreateUser(ctx, sqlcgen.CreateUserParams{
AccessHash: u.AccessHash,
Phone: u.Phone,
SignupEmail: u.SignupEmail,
FirstName: u.FirstName,
LastName: u.LastName,
Username: u.Username,
@ -448,6 +467,7 @@ func userFromModel(r sqlcgen.User) domain.User {
ID: r.ID,
AccessHash: r.AccessHash,
Phone: r.Phone,
SignupEmail: r.SignupEmail,
FirstName: r.FirstName,
LastName: r.LastName,
About: r.About,

View file

@ -12,6 +12,10 @@ type UserStore interface {
ByIDs(ctx context.Context, ids []int64) ([]domain.User, error)
ByPhone(ctx context.Context, phone string) (domain.User, bool, error)
ByPhones(ctx context.Context, phones []string) ([]domain.User, error)
// ByEmail looks up an email-signup account by its signup_email (see
// domain.NewEmailSignupDisplayPhone). Ordinary phone accounts never
// match: signup_email is empty for them.
ByEmail(ctx context.Context, email string) (domain.User, bool, error)
ByUsername(ctx context.Context, username string) (domain.User, bool, error)
Search(ctx context.Context, currentUserID int64, query, phoneQuery string, limit int) (domain.UserSearchResult, error)
UpdateProfile(ctx context.Context, userID int64, firstName, lastName, about string) (domain.User, error)