feat(server/auth): switch user identity to phone with email optional
Phone becomes the required, unique login identifier; email and name turn optional. Login now accepts either via a single identifier field, refresh re-checks tenant membership, and the dev seed provides phone numbers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,17 +101,17 @@ WHERE email = $1 AND deleted_at IS NULL
|
||||
|
||||
type GetUserByEmailRow struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Phone pgtype.Text `json:"phone"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
Name string `json:"name"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) {
|
||||
func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error) {
|
||||
row := q.db.QueryRow(ctx, getUserByEmail, email)
|
||||
var i GetUserByEmailRow
|
||||
err := row.Scan(
|
||||
@@ -136,10 +136,10 @@ WHERE id = $1 AND deleted_at IS NULL
|
||||
|
||||
type GetUserByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Phone pgtype.Text `json:"phone"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
Name string `json:"name"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
@@ -162,3 +162,39 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, er
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByLoginIdentifier = `-- name: GetUserByLoginIdentifier :one
|
||||
SELECT id, email, phone, password_hash, name, avatar_url, status, created_at, updated_at
|
||||
FROM users
|
||||
WHERE (email = $1 OR phone = $1)
|
||||
AND deleted_at IS NULL
|
||||
`
|
||||
|
||||
type GetUserByLoginIdentifierRow struct {
|
||||
ID int64 `json:"id"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByLoginIdentifier(ctx context.Context, loginIdentifier pgtype.Text) (GetUserByLoginIdentifierRow, error) {
|
||||
row := q.db.QueryRow(ctx, getUserByLoginIdentifier, loginIdentifier)
|
||||
var i GetUserByLoginIdentifierRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Email,
|
||||
&i.Phone,
|
||||
&i.PasswordHash,
|
||||
&i.Name,
|
||||
&i.AvatarUrl,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -21,6 +21,27 @@ type AiPlatform struct {
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type AiPointUsageLog struct {
|
||||
ID int64 `json:"id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
OperatorID pgtype.Int8 `json:"operator_id"`
|
||||
QuotaReservationID pgtype.Int8 `json:"quota_reservation_id"`
|
||||
UsageType string `json:"usage_type"`
|
||||
ResourceType pgtype.Text `json:"resource_type"`
|
||||
ResourceID pgtype.Int8 `json:"resource_id"`
|
||||
ResourceUid pgtype.Text `json:"resource_uid"`
|
||||
RequestChars int32 `json:"request_chars"`
|
||||
BaseChars int32 `json:"base_chars"`
|
||||
Points int32 `json:"points"`
|
||||
Status string `json:"status"`
|
||||
Model pgtype.Text `json:"model"`
|
||||
MetadataJson []byte `json:"metadata_json"`
|
||||
ErrorMessage pgtype.Text `json:"error_message"`
|
||||
CompletedAt pgtype.Timestamptz `json:"completed_at"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Article struct {
|
||||
ID int64 `json:"id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
@@ -149,6 +170,15 @@ type DesktopClient struct {
|
||||
RevokedAt pgtype.Timestamptz `json:"revoked_at"`
|
||||
}
|
||||
|
||||
type DesktopClientPrimaryLease struct {
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
PrimaryClientID pgtype.UUID `json:"primary_client_id"`
|
||||
LeaseExpiresAt pgtype.Timestamptz `json:"lease_expires_at"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type DesktopPublishJob struct {
|
||||
ID int64 `json:"id"`
|
||||
DesktopID pgtype.UUID `json:"desktop_id"`
|
||||
@@ -708,10 +738,10 @@ type TenantQuotaLedger struct {
|
||||
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Phone pgtype.Text `json:"phone"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
Name string `json:"name"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
|
||||
@@ -6,6 +6,8 @@ package generated
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
@@ -90,8 +92,9 @@ type Querier interface {
|
||||
GetTemplateByID(ctx context.Context, arg GetTemplateByIDParams) (GetTemplateByIDRow, error)
|
||||
GetTenantMembership(ctx context.Context, userID int64) (GetTenantMembershipRow, error)
|
||||
GetTenantMembershipByTenantAndUser(ctx context.Context, arg GetTenantMembershipByTenantAndUserParams) (GetTenantMembershipByTenantAndUserRow, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
|
||||
GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error)
|
||||
GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error)
|
||||
GetUserByLoginIdentifier(ctx context.Context, loginIdentifier pgtype.Text) (GetUserByLoginIdentifierRow, error)
|
||||
HeartbeatDesktopClient(ctx context.Context, arg HeartbeatDesktopClientParams) (DesktopClient, error)
|
||||
InsertImageAsset(ctx context.Context, arg InsertImageAssetParams) (int64, error)
|
||||
InsertQuotaLedger(ctx context.Context, arg InsertQuotaLedgerParams) (int64, error)
|
||||
|
||||
Reference in New Issue
Block a user