diff --git a/internal/infrastructure/database/repository.go b/internal/infrastructure/database/repository.go index d5d0e89..af90762 100644 --- a/internal/infrastructure/database/repository.go +++ b/internal/infrastructure/database/repository.go @@ -18,6 +18,37 @@ func NewSignatureRepository(db *sql.DB) *SignatureRepository { return &SignatureRepository{db: db} } +// scanSignature scans a row into a Signature, handling NULL values properly +func scanSignature(scanner interface { + Scan(dest ...interface{}) error +}, signature *models.Signature) error { + var userName sql.NullString + err := scanner.Scan( + &signature.ID, + &signature.DocID, + &signature.UserSub, + &signature.UserEmail, + &userName, + &signature.SignedAtUTC, + &signature.PayloadHash, + &signature.Signature, + &signature.Nonce, + &signature.CreatedAt, + &signature.Referer, + &signature.PrevHash, + ) + if err != nil { + return err + } + // Convert sql.NullString to string (empty string if NULL) + if userName.Valid { + signature.UserName = userName.String + } else { + signature.UserName = "" + } + return nil +} + func (r *SignatureRepository) Create(ctx context.Context, signature *models.Signature) error { query := ` INSERT INTO signatures (doc_id, user_sub, user_email, user_name, signed_at, payload_hash, signature, nonce, referer, prev_hash) @@ -25,12 +56,18 @@ func (r *SignatureRepository) Create(ctx context.Context, signature *models.Sign RETURNING id, created_at ` + // Convert empty string to NULL for user_name + var userName sql.NullString + if signature.UserName != "" { + userName = sql.NullString{String: signature.UserName, Valid: true} + } + err := r.db.QueryRowContext( ctx, query, signature.DocID, signature.UserSub, signature.UserEmail, - signature.UserName, + userName, signature.SignedAtUTC, signature.PayloadHash, signature.Signature, @@ -49,25 +86,12 @@ func (r *SignatureRepository) Create(ctx context.Context, signature *models.Sign func (r *SignatureRepository) GetByDocAndUser(ctx context.Context, docID, userSub string) (*models.Signature, error) { query := ` SELECT id, doc_id, user_sub, user_email, user_name, signed_at, payload_hash, signature, nonce, created_at, referer, prev_hash - FROM signatures + FROM signatures WHERE doc_id = $1 AND user_sub = $2 ` signature := &models.Signature{} - err := r.db.QueryRowContext(ctx, query, docID, userSub).Scan( - &signature.ID, - &signature.DocID, - &signature.UserSub, - &signature.UserEmail, - &signature.UserName, - &signature.SignedAtUTC, - &signature.PayloadHash, - &signature.Signature, - &signature.Nonce, - &signature.CreatedAt, - &signature.Referer, - &signature.PrevHash, - ) + err := scanSignature(r.db.QueryRowContext(ctx, query, docID, userSub), signature) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -98,21 +122,7 @@ func (r *SignatureRepository) GetByDoc(ctx context.Context, docID string) ([]*mo var signatures []*models.Signature for rows.Next() { signature := &models.Signature{} - err := rows.Scan( - &signature.ID, - &signature.DocID, - &signature.UserSub, - &signature.UserEmail, - &signature.UserName, - &signature.SignedAtUTC, - &signature.PayloadHash, - &signature.Signature, - &signature.Nonce, - &signature.CreatedAt, - &signature.Referer, - &signature.PrevHash, - ) - if err != nil { + if err := scanSignature(rows, signature); err != nil { continue } signatures = append(signatures, signature) @@ -140,21 +150,7 @@ func (r *SignatureRepository) GetByUser(ctx context.Context, userSub string) ([] var signatures []*models.Signature for rows.Next() { signature := &models.Signature{} - err := rows.Scan( - &signature.ID, - &signature.DocID, - &signature.UserSub, - &signature.UserEmail, - &signature.UserName, - &signature.SignedAtUTC, - &signature.PayloadHash, - &signature.Signature, - &signature.Nonce, - &signature.CreatedAt, - &signature.Referer, - &signature.PrevHash, - ) - if err != nil { + if err := scanSignature(rows, signature); err != nil { continue } signatures = append(signatures, signature) @@ -195,26 +191,13 @@ func (r *SignatureRepository) CheckUserSignatureStatus(ctx context.Context, docI func (r *SignatureRepository) GetLastSignature(ctx context.Context) (*models.Signature, error) { query := ` SELECT id, doc_id, user_sub, user_email, user_name, signed_at, payload_hash, signature, nonce, created_at, referer, prev_hash - FROM signatures - ORDER BY id DESC + FROM signatures + ORDER BY id DESC LIMIT 1 ` signature := &models.Signature{} - err := r.db.QueryRowContext(ctx, query).Scan( - &signature.ID, - &signature.DocID, - &signature.UserSub, - &signature.UserEmail, - &signature.UserName, - &signature.SignedAtUTC, - &signature.PayloadHash, - &signature.Signature, - &signature.Nonce, - &signature.CreatedAt, - &signature.Referer, - &signature.PrevHash, - ) + err := scanSignature(r.db.QueryRowContext(ctx, query), signature) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -243,21 +226,7 @@ func (r *SignatureRepository) GetAllSignaturesOrdered(ctx context.Context) ([]*m var signatures []*models.Signature for rows.Next() { signature := &models.Signature{} - err := rows.Scan( - &signature.ID, - &signature.DocID, - &signature.UserSub, - &signature.UserEmail, - &signature.UserName, - &signature.SignedAtUTC, - &signature.PayloadHash, - &signature.Signature, - &signature.Nonce, - &signature.CreatedAt, - &signature.Referer, - &signature.PrevHash, - ) - if err != nil { + if err := scanSignature(rows, signature); err != nil { continue } signatures = append(signatures, signature) diff --git a/internal/infrastructure/database/repository_constraints_test.go b/internal/infrastructure/database/repository_constraints_test.go index 2ec12d9..198a0ec 100644 --- a/internal/infrastructure/database/repository_constraints_test.go +++ b/internal/infrastructure/database/repository_constraints_test.go @@ -62,7 +62,7 @@ func TestRepository_DatabaseConstraints_Integration(t *testing.T) { { name: "valid signature with nulls", modifyFn: func(s *models.Signature) { - s.UserName = nil + s.UserName = "" s.Referer = nil s.PrevHash = nil }, @@ -403,7 +403,7 @@ func TestRepository_EdgeCases_Integration(t *testing.T) { // Test with empty strings for nullable fields sig := factory.CreateValidSignature() emptyString := "" - sig.UserName = &emptyString + sig.UserName = emptyString sig.Referer = &emptyString sig.PrevHash = &emptyString @@ -418,7 +418,7 @@ func TestRepository_EdgeCases_Integration(t *testing.T) { } // Verify empty strings are preserved (not converted to NULL) - if result.UserName == nil || *result.UserName != "" { + if result.UserName != "" { t.Error("Empty string UserName not preserved") } if result.Referer == nil || *result.Referer != "" {