| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- package postgres
- import (
- "context"
- "errors"
- "fmt"
- "time"
- "github.com/jackc/pgx/v5"
- )
- type OpsUser struct {
- ID string
- PublicID string
- CountryCode string
- Mobile string
- DisplayName string
- Status string
- LastLoginAt *time.Time
- }
- type OpsRole struct {
- ID string
- RoleCode string
- DisplayName string
- RoleRank int
- }
- type CreateOpsUserParams struct {
- PublicID string
- CountryCode string
- Mobile string
- DisplayName string
- Status string
- }
- type CreateOpsRefreshTokenParams struct {
- OpsUserID string
- DeviceKey string
- TokenHash string
- ExpiresAt time.Time
- }
- type OpsRefreshTokenRecord struct {
- ID string
- OpsUserID string
- DeviceKey *string
- ExpiresAt time.Time
- IsRevoked bool
- }
- func (s *Store) CountOpsUsers(ctx context.Context) (int, error) {
- row := s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM ops_users WHERE status <> 'deleted'`)
- var count int
- if err := row.Scan(&count); err != nil {
- return 0, fmt.Errorf("count ops users: %w", err)
- }
- return count, nil
- }
- func (s *Store) GetOpsUserByMobile(ctx context.Context, db queryRower, countryCode, mobile string) (*OpsUser, error) {
- row := db.QueryRow(ctx, `
- SELECT id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
- FROM ops_users
- WHERE country_code = $1 AND mobile = $2
- LIMIT 1
- `, countryCode, mobile)
- return scanOpsUser(row)
- }
- func (s *Store) GetOpsUserByID(ctx context.Context, db queryRower, opsUserID string) (*OpsUser, error) {
- row := db.QueryRow(ctx, `
- SELECT id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
- FROM ops_users
- WHERE id = $1
- `, opsUserID)
- return scanOpsUser(row)
- }
- func (s *Store) CreateOpsUser(ctx context.Context, tx Tx, params CreateOpsUserParams) (*OpsUser, error) {
- row := tx.QueryRow(ctx, `
- INSERT INTO ops_users (ops_user_public_id, country_code, mobile, display_name, status)
- VALUES ($1, $2, $3, $4, $5)
- RETURNING id, ops_user_public_id, country_code, mobile, display_name, status, last_login_at
- `, params.PublicID, params.CountryCode, params.Mobile, params.DisplayName, params.Status)
- return scanOpsUser(row)
- }
- func (s *Store) TouchOpsUserLogin(ctx context.Context, tx Tx, opsUserID string) error {
- _, err := tx.Exec(ctx, `
- UPDATE ops_users
- SET last_login_at = NOW()
- WHERE id = $1
- `, opsUserID)
- if err != nil {
- return fmt.Errorf("touch ops user last login: %w", err)
- }
- return nil
- }
- func (s *Store) GetOpsRoleByCode(ctx context.Context, tx Tx, roleCode string) (*OpsRole, error) {
- row := tx.QueryRow(ctx, `
- SELECT id, role_code, display_name, role_rank
- FROM ops_roles
- WHERE role_code = $1
- AND status = 'active'
- LIMIT 1
- `, roleCode)
- return scanOpsRole(row)
- }
- func (s *Store) AssignOpsRole(ctx context.Context, tx Tx, opsUserID, roleID string) error {
- _, err := tx.Exec(ctx, `
- INSERT INTO ops_user_roles (ops_user_id, ops_role_id, status)
- VALUES ($1, $2, 'active')
- ON CONFLICT (ops_user_id, ops_role_id) DO NOTHING
- `, opsUserID, roleID)
- if err != nil {
- return fmt.Errorf("assign ops role: %w", err)
- }
- return nil
- }
- func (s *Store) GetPrimaryOpsRole(ctx context.Context, db queryRower, opsUserID string) (*OpsRole, error) {
- row := db.QueryRow(ctx, `
- SELECT r.id, r.role_code, r.display_name, r.role_rank
- FROM ops_user_roles ur
- JOIN ops_roles r ON r.id = ur.ops_role_id
- WHERE ur.ops_user_id = $1
- AND ur.status = 'active'
- AND r.status = 'active'
- ORDER BY r.role_rank DESC, r.created_at ASC
- LIMIT 1
- `, opsUserID)
- return scanOpsRole(row)
- }
- func (s *Store) CreateOpsRefreshToken(ctx context.Context, tx Tx, params CreateOpsRefreshTokenParams) (string, error) {
- row := tx.QueryRow(ctx, `
- INSERT INTO ops_refresh_tokens (ops_user_id, device_key, token_hash, expires_at)
- VALUES ($1, NULLIF($2, ''), $3, $4)
- RETURNING id
- `, params.OpsUserID, params.DeviceKey, params.TokenHash, params.ExpiresAt)
- var id string
- if err := row.Scan(&id); err != nil {
- return "", fmt.Errorf("create ops refresh token: %w", err)
- }
- return id, nil
- }
- func (s *Store) GetOpsRefreshTokenForUpdate(ctx context.Context, tx Tx, tokenHash string) (*OpsRefreshTokenRecord, error) {
- row := tx.QueryRow(ctx, `
- SELECT id, ops_user_id, device_key, expires_at, revoked_at IS NOT NULL
- FROM ops_refresh_tokens
- WHERE token_hash = $1
- FOR UPDATE
- `, tokenHash)
- var record OpsRefreshTokenRecord
- err := row.Scan(&record.ID, &record.OpsUserID, &record.DeviceKey, &record.ExpiresAt, &record.IsRevoked)
- if errors.Is(err, pgx.ErrNoRows) {
- return nil, nil
- }
- if err != nil {
- return nil, fmt.Errorf("query ops refresh token for update: %w", err)
- }
- return &record, nil
- }
- func (s *Store) RotateOpsRefreshToken(ctx context.Context, tx Tx, oldTokenID, newTokenID string) error {
- _, err := tx.Exec(ctx, `
- UPDATE ops_refresh_tokens
- SET revoked_at = NOW(), replaced_by_token_id = $2
- WHERE id = $1
- `, oldTokenID, newTokenID)
- if err != nil {
- return fmt.Errorf("rotate ops refresh token: %w", err)
- }
- return nil
- }
- func (s *Store) RevokeOpsRefreshToken(ctx context.Context, tokenHash string) error {
- _, err := s.pool.Exec(ctx, `
- UPDATE ops_refresh_tokens
- SET revoked_at = COALESCE(revoked_at, NOW())
- WHERE token_hash = $1
- `, tokenHash)
- if err != nil {
- return fmt.Errorf("revoke ops refresh token: %w", err)
- }
- return nil
- }
- func scanOpsUser(row pgx.Row) (*OpsUser, error) {
- var item OpsUser
- err := row.Scan(&item.ID, &item.PublicID, &item.CountryCode, &item.Mobile, &item.DisplayName, &item.Status, &item.LastLoginAt)
- if errors.Is(err, pgx.ErrNoRows) {
- return nil, nil
- }
- if err != nil {
- return nil, fmt.Errorf("scan ops user: %w", err)
- }
- return &item, nil
- }
- func scanOpsRole(row pgx.Row) (*OpsRole, error) {
- var item OpsRole
- err := row.Scan(&item.ID, &item.RoleCode, &item.DisplayName, &item.RoleRank)
- if errors.Is(err, pgx.ErrNoRows) {
- return nil, nil
- }
- if err != nil {
- return nil, fmt.Errorf("scan ops role: %w", err)
- }
- return &item, nil
- }
|