92 lines
3.6 KiB
Go
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))
|
|
}
|