feat(account): stamp last-login time + IP on cold app-load
The profile GET (fetched once per cold app-load by the SPA) stamps accounts .last_login_at/.last_login_ip, throttled to at most once per hour per account (best-effort, never blocks the read). IP from the gateway-forwarded X-Forwarded-For. Feeds the account-deletion dossier. Integration test covers the throttle.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"scrabble/backend/internal/postgres/jet/backend/table"
|
||||
@@ -41,3 +42,24 @@ func retainIdentityTx(ctx context.Context, tx *sql.Tx, accountID uuid.UUID, kind
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StampLastLogin records the account's last cold-load time and client IP, but only when
|
||||
// the stored value is missing or older than an hour — so it costs at most one write per
|
||||
// account per hour (its caller, the profile fetch, runs once per cold app-load). It is a
|
||||
// best-effort audit signal that feeds the account-deletion dossier.
|
||||
func (s *Store) StampLastLogin(ctx context.Context, accountID uuid.UUID, ip string) error {
|
||||
now := time.Now().UTC()
|
||||
upd := table.Accounts.UPDATE(table.Accounts.LastLoginAt, table.Accounts.LastLoginIP).
|
||||
SET(postgres.TimestampzT(now), postgres.String(ip)).
|
||||
WHERE(
|
||||
table.Accounts.AccountID.EQ(postgres.UUID(accountID)).
|
||||
AND(
|
||||
table.Accounts.LastLoginAt.IS_NULL().
|
||||
OR(table.Accounts.LastLoginAt.LT(postgres.TimestampzT(now.Add(-time.Hour)))),
|
||||
),
|
||||
)
|
||||
if _, err := upd.ExecContext(ctx, s.db); err != nil {
|
||||
return fmt.Errorf("account: stamp last login %s: %w", accountID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user