fix: properly handle NULL UserName in database operations

Add scanSignature helper to convert sql.NullString to string type.
Update Create method to insert NULL for empty UserName values.
Fix integration tests to work with string type instead of pointer.
This commit is contained in:
Benjamin
2025-10-02 21:03:00 +02:00
parent d6dd3625a9
commit 06e4b33167
2 changed files with 49 additions and 80 deletions

View File

@@ -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)

View File

@@ -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 != "" {