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:
2026-04-29 00:01:36 +08:00
parent a6b17bae62
commit ee21f512a9
11 changed files with 225 additions and 40 deletions
+32 -10
View File
@@ -4,15 +4,17 @@ import (
"context"
"time"
"github.com/jackc/pgx/v5/pgtype"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type UserRecord struct {
ID int64
Email string
Phone *string
Email *string
Phone string
PasswordHash string
Name string
Name *string
AvatarURL *string
Status string
CreatedAt time.Time
@@ -30,6 +32,7 @@ type MembershipRecord struct {
type AuthRepository interface {
GetUserByEmail(ctx context.Context, email string) (*UserRecord, error)
GetUserByLoginIdentifier(ctx context.Context, identifier string) (*UserRecord, error)
GetUserByID(ctx context.Context, id int64) (*UserRecord, error)
GetTenantMembership(ctx context.Context, userID int64) (*MembershipRecord, error)
GetTenantMembershipByTenantAndUser(ctx context.Context, tenantID, userID int64) (*MembershipRecord, error)
@@ -44,17 +47,36 @@ func NewAuthRepository(db generated.DBTX) AuthRepository {
}
func (r *authRepository) GetUserByEmail(ctx context.Context, email string) (*UserRecord, error) {
row, err := r.q.GetUserByEmail(ctx, email)
row, err := r.q.GetUserByEmail(ctx, pgtype.Text{String: email, Valid: true})
if err != nil {
return nil, err
}
return &UserRecord{
ID: row.ID,
Email: row.Email,
Phone: nullableText(row.Phone),
Email: nullableText(row.Email),
Phone: row.Phone,
PasswordHash: row.PasswordHash,
Name: row.Name,
Name: nullableText(row.Name),
AvatarURL: nullableText(row.AvatarUrl),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *authRepository) GetUserByLoginIdentifier(ctx context.Context, identifier string) (*UserRecord, error) {
row, err := r.q.GetUserByLoginIdentifier(ctx, pgtype.Text{String: identifier, Valid: true})
if err != nil {
return nil, err
}
return &UserRecord{
ID: row.ID,
Email: nullableText(row.Email),
Phone: row.Phone,
PasswordHash: row.PasswordHash,
Name: nullableText(row.Name),
AvatarURL: nullableText(row.AvatarUrl),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
@@ -70,10 +92,10 @@ func (r *authRepository) GetUserByID(ctx context.Context, id int64) (*UserRecord
return &UserRecord{
ID: row.ID,
Email: row.Email,
Phone: nullableText(row.Phone),
Email: nullableText(row.Email),
Phone: row.Phone,
PasswordHash: row.PasswordHash,
Name: row.Name,
Name: nullableText(row.Name),
AvatarURL: nullableText(row.AvatarUrl),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),