Support both email and phone login
Auto-detect whether the user entered an email or phone number. Email sends via Resend, phone sends via Twilio SMS. Users table has nullable phone and email columns; verification_codes uses a generic identifier field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+7
-6
@@ -55,15 +55,16 @@ type Slot struct {
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Phone string
|
||||
Phone sql.NullString
|
||||
Email sql.NullString
|
||||
Name string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type VerificationCode struct {
|
||||
ID int64
|
||||
Phone string
|
||||
Code string
|
||||
ExpiresAt time.Time
|
||||
Used int64
|
||||
ID int64
|
||||
Identifier string
|
||||
Code string
|
||||
ExpiresAt time.Time
|
||||
Used int64
|
||||
}
|
||||
|
||||
+9
-3
@@ -72,9 +72,15 @@ SELECT COUNT(*) FROM rsvps WHERE event_id = ?;
|
||||
-- name: GetUserByPhone :one
|
||||
SELECT * FROM users WHERE phone = ?;
|
||||
|
||||
-- name: CreateUser :one
|
||||
-- name: GetUserByEmail :one
|
||||
SELECT * FROM users WHERE email = ?;
|
||||
|
||||
-- name: CreateUserByPhone :one
|
||||
INSERT INTO users (phone, name) VALUES (?, '') RETURNING *;
|
||||
|
||||
-- name: CreateUserByEmail :one
|
||||
INSERT INTO users (email, name) VALUES (?, '') RETURNING *;
|
||||
|
||||
-- name: UpdateUserName :exec
|
||||
UPDATE users SET name = ? WHERE id = ?;
|
||||
|
||||
@@ -91,11 +97,11 @@ DELETE FROM sessions WHERE token = ?;
|
||||
DELETE FROM sessions WHERE expires_at <= CURRENT_TIMESTAMP;
|
||||
|
||||
-- name: CreateVerificationCode :exec
|
||||
INSERT INTO verification_codes (phone, code, expires_at) VALUES (?, ?, ?);
|
||||
INSERT INTO verification_codes (identifier, code, expires_at) VALUES (?, ?, ?);
|
||||
|
||||
-- name: GetVerificationCode :one
|
||||
SELECT * FROM verification_codes
|
||||
WHERE phone = ? AND code = ? AND used = 0 AND expires_at > CURRENT_TIMESTAMP
|
||||
WHERE identifier = ? AND code = ? AND used = 0 AND expires_at > CURRENT_TIMESTAMP
|
||||
ORDER BY id DESC LIMIT 1;
|
||||
|
||||
-- name: MarkVerificationCodeUsed :exec
|
||||
|
||||
+54
-18
@@ -174,16 +174,34 @@ func (q *Queries) CreateSlot(ctx context.Context, arg CreateSlotParams) (Slot, e
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO users (phone, name) VALUES (?, '') RETURNING id, phone, name, created_at
|
||||
const createUserByEmail = `-- name: CreateUserByEmail :one
|
||||
INSERT INTO users (email, name) VALUES (?, '') RETURNING id, phone, email, name, created_at
|
||||
`
|
||||
|
||||
func (q *Queries) CreateUser(ctx context.Context, phone string) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, createUser, phone)
|
||||
func (q *Queries) CreateUserByEmail(ctx context.Context, email sql.NullString) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, createUserByEmail, email)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Phone,
|
||||
&i.Email,
|
||||
&i.Name,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createUserByPhone = `-- name: CreateUserByPhone :one
|
||||
INSERT INTO users (phone, name) VALUES (?, '') RETURNING id, phone, email, name, created_at
|
||||
`
|
||||
|
||||
func (q *Queries) CreateUserByPhone(ctx context.Context, phone sql.NullString) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, createUserByPhone, phone)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Phone,
|
||||
&i.Email,
|
||||
&i.Name,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
@@ -191,17 +209,17 @@ func (q *Queries) CreateUser(ctx context.Context, phone string) (User, error) {
|
||||
}
|
||||
|
||||
const createVerificationCode = `-- name: CreateVerificationCode :exec
|
||||
INSERT INTO verification_codes (phone, code, expires_at) VALUES (?, ?, ?)
|
||||
INSERT INTO verification_codes (identifier, code, expires_at) VALUES (?, ?, ?)
|
||||
`
|
||||
|
||||
type CreateVerificationCodeParams struct {
|
||||
Phone string
|
||||
Code string
|
||||
ExpiresAt time.Time
|
||||
Identifier string
|
||||
Code string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func (q *Queries) CreateVerificationCode(ctx context.Context, arg CreateVerificationCodeParams) error {
|
||||
_, err := q.db.ExecContext(ctx, createVerificationCode, arg.Phone, arg.Code, arg.ExpiresAt)
|
||||
_, err := q.db.ExecContext(ctx, createVerificationCode, arg.Identifier, arg.Code, arg.ExpiresAt)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -332,16 +350,34 @@ func (q *Queries) GetSlot(ctx context.Context, id int64) (Slot, error) {
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByPhone = `-- name: GetUserByPhone :one
|
||||
SELECT id, phone, name, created_at FROM users WHERE phone = ?
|
||||
const getUserByEmail = `-- name: GetUserByEmail :one
|
||||
SELECT id, phone, email, name, created_at FROM users WHERE email = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error) {
|
||||
func (q *Queries) GetUserByEmail(ctx context.Context, email sql.NullString) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUserByEmail, email)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Phone,
|
||||
&i.Email,
|
||||
&i.Name,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByPhone = `-- name: GetUserByPhone :one
|
||||
SELECT id, phone, email, name, created_at FROM users WHERE phone = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByPhone(ctx context.Context, phone sql.NullString) (User, error) {
|
||||
row := q.db.QueryRowContext(ctx, getUserByPhone, phone)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Phone,
|
||||
&i.Email,
|
||||
&i.Name,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
@@ -349,22 +385,22 @@ func (q *Queries) GetUserByPhone(ctx context.Context, phone string) (User, error
|
||||
}
|
||||
|
||||
const getVerificationCode = `-- name: GetVerificationCode :one
|
||||
SELECT id, phone, code, expires_at, used FROM verification_codes
|
||||
WHERE phone = ? AND code = ? AND used = 0 AND expires_at > CURRENT_TIMESTAMP
|
||||
SELECT id, identifier, code, expires_at, used FROM verification_codes
|
||||
WHERE identifier = ? AND code = ? AND used = 0 AND expires_at > CURRENT_TIMESTAMP
|
||||
ORDER BY id DESC LIMIT 1
|
||||
`
|
||||
|
||||
type GetVerificationCodeParams struct {
|
||||
Phone string
|
||||
Code string
|
||||
Identifier string
|
||||
Code string
|
||||
}
|
||||
|
||||
func (q *Queries) GetVerificationCode(ctx context.Context, arg GetVerificationCodeParams) (VerificationCode, error) {
|
||||
row := q.db.QueryRowContext(ctx, getVerificationCode, arg.Phone, arg.Code)
|
||||
row := q.db.QueryRowContext(ctx, getVerificationCode, arg.Identifier, arg.Code)
|
||||
var i VerificationCode
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Phone,
|
||||
&i.Identifier,
|
||||
&i.Code,
|
||||
&i.ExpiresAt,
|
||||
&i.Used,
|
||||
|
||||
Reference in New Issue
Block a user