e045e00fbf
Add self-service password change that re-hashes the credential and bumps a per-user session version so all existing access/refresh tokens are rejected. Session version is tracked in Redis with an in-memory fallback and enforced in middleware and refresh. Scope prompt rules and rule groups to a brand: new brand_id column (migrated to a "未归属" fallback brand for existing rows), brand-filtered queries/indexes, and brand-aware admin-web tabs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
package repository
|
|
|
|
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
|
|
PasswordHash string
|
|
Name *string
|
|
AvatarURL *string
|
|
Status string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type MembershipRecord struct {
|
|
ID int64
|
|
TenantID int64
|
|
UserID int64
|
|
PrimaryWorkspaceID int64
|
|
TenantRole string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
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)
|
|
UpdateUserPassword(ctx context.Context, id int64, passwordHash string) error
|
|
GetTenantMembership(ctx context.Context, userID int64) (*MembershipRecord, error)
|
|
GetTenantMembershipByTenantAndUser(ctx context.Context, tenantID, userID int64) (*MembershipRecord, error)
|
|
}
|
|
|
|
type authRepository struct {
|
|
q generated.Querier
|
|
}
|
|
|
|
func NewAuthRepository(db generated.DBTX) AuthRepository {
|
|
return &authRepository{q: newQuerier(db)}
|
|
}
|
|
|
|
func (r *authRepository) GetUserByEmail(ctx context.Context, email string) (*UserRecord, error) {
|
|
row, err := r.q.GetUserByEmail(ctx, pgtype.Text{String: email, 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),
|
|
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),
|
|
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
|
|
}, nil
|
|
}
|
|
|
|
func (r *authRepository) GetUserByID(ctx context.Context, id int64) (*UserRecord, error) {
|
|
row, err := r.q.GetUserByID(ctx, id)
|
|
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),
|
|
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
|
|
}, nil
|
|
}
|
|
|
|
func (r *authRepository) UpdateUserPassword(ctx context.Context, id int64, passwordHash string) error {
|
|
return r.q.UpdateUserPassword(ctx, generated.UpdateUserPasswordParams{
|
|
ID: id,
|
|
PasswordHash: passwordHash,
|
|
})
|
|
}
|
|
|
|
func (r *authRepository) GetTenantMembership(ctx context.Context, userID int64) (*MembershipRecord, error) {
|
|
row, err := r.q.GetTenantMembership(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &MembershipRecord{
|
|
ID: row.ID,
|
|
TenantID: row.TenantID,
|
|
UserID: row.UserID,
|
|
PrimaryWorkspaceID: row.PrimaryWorkspaceID,
|
|
TenantRole: row.TenantRole,
|
|
CreatedAt: timeFromTimestamp(row.CreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
func (r *authRepository) GetTenantMembershipByTenantAndUser(ctx context.Context, tenantID, userID int64) (*MembershipRecord, error) {
|
|
row, err := r.q.GetTenantMembershipByTenantAndUser(ctx, generated.GetTenantMembershipByTenantAndUserParams{
|
|
TenantID: tenantID,
|
|
UserID: userID,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &MembershipRecord{
|
|
ID: row.ID,
|
|
TenantID: row.TenantID,
|
|
UserID: row.UserID,
|
|
PrimaryWorkspaceID: row.PrimaryWorkspaceID,
|
|
TenantRole: row.TenantRole,
|
|
CreatedAt: timeFromTimestamp(row.CreatedAt),
|
|
}, nil
|
|
}
|