Files
bot-pontos/backend/internal/adapters/postgres/credentials.go
2026-10-23 22:31:02 -03:00

92 lines
3.6 KiB
Go

package postgres
import (
"context"
"time"
"botpontos/internal/domain"
)
func credentialCols(p string) string {
return cols(p, "user_id", "api_email", "encrypted_password", "key_version", "last_validated_at",
"last_error", "consecutive_failures", "last_failure_at", "updated_at")
}
type credentialScan struct {
c domain.Credential
}
func (s *credentialScan) dest() []any {
return []any{&s.c.UserID, &s.c.APIEmail, &s.c.EncryptedPassword, &s.c.KeyVersion, &s.c.LastValidatedAt,
&s.c.LastError, &s.c.ConsecutiveFailures, &s.c.LastFailureAt, &s.c.UpdatedAt}
}
func (s *credentialScan) result() domain.Credential { return s.c }
func readCredential(row scanner) (domain.Credential, error) {
var cs credentialScan
if err := row.Scan(cs.dest()...); err != nil {
return domain.Credential{}, mapErr(err)
}
return cs.result(), nil
}
func (s *Store) GetCredential(ctx context.Context, userID int64) (domain.Credential, error) {
return readCredential(s.pool.QueryRow(ctx, `SELECT `+credentialCols("")+` FROM point_credentials WHERE user_id = $1`, userID))
}
// SaveCredential grava uma credencial já validada na PontoMais e zera o histórico de falhas.
func (s *Store) SaveCredential(ctx context.Context, userID int64, email string, encrypted []byte, keyVersion int16, validatedAt time.Time) error {
_, err := s.pool.Exec(ctx, `
INSERT INTO point_credentials (user_id, api_email, encrypted_password, key_version, last_validated_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (user_id) DO UPDATE SET
api_email = EXCLUDED.api_email,
encrypted_password = EXCLUDED.encrypted_password,
key_version = EXCLUDED.key_version,
last_validated_at = EXCLUDED.last_validated_at,
last_error = NULL,
consecutive_failures = 0,
last_failure_at = NULL,
updated_at = now()`,
userID, email, encrypted, keyVersion, validatedAt)
return mapErr(err)
}
// MarkCredentialValidated registra um teste bem-sucedido da credencial atual.
func (s *Store) MarkCredentialValidated(ctx context.Context, userID int64, at time.Time) error {
return expectOne(s.pool.Exec(ctx, `
UPDATE point_credentials SET last_validated_at = $2, last_error = NULL WHERE user_id = $1`, userID, at))
}
// RecordCredentialFailure soma uma falha consecutiva e devolve o total (9.4).
func (s *Store) RecordCredentialFailure(ctx context.Context, userID int64, message string, at time.Time) (int, error) {
var failures int
err := s.pool.QueryRow(ctx, `
UPDATE point_credentials
SET consecutive_failures = consecutive_failures + 1, last_error = $2, last_failure_at = $3
WHERE user_id = $1
RETURNING consecutive_failures`, userID, message, at).Scan(&failures)
return failures, mapErr(err)
}
// ResetCredentialFailures zera o contador após uma consulta bem-sucedida.
func (s *Store) ResetCredentialFailures(ctx context.Context, userID int64) error {
_, err := s.pool.Exec(ctx, `
UPDATE point_credentials SET consecutive_failures = 0, last_error = NULL
WHERE user_id = $1 AND (consecutive_failures > 0 OR last_error IS NOT NULL)`, userID)
return err
}
func (s *Store) ListCredentials(ctx context.Context) ([]domain.Credential, error) {
rows, err := s.pool.Query(ctx, `SELECT `+credentialCols("")+` FROM point_credentials ORDER BY user_id`)
return collect(rows, err, readCredential)
}
// UpdateCredentialCipher regrava a senha cifrada com outra chave (rotação).
func (s *Store) UpdateCredentialCipher(ctx context.Context, userID int64, encrypted []byte, keyVersion int16) error {
return expectOne(s.pool.Exec(ctx, `
UPDATE point_credentials SET encrypted_password = $2, key_version = $3, updated_at = now() WHERE user_id = $1`,
userID, encrypted, keyVersion))
}