fix: address code review findings

- Add is_ignored=0 filter to GetUnnotifiedReleases query per spec section 4.4
  (notification lifecycle must exclude ignored releases)
- Add FK constraint on notifications_sent.rgid referencing external_releases(rgid)
  per spec schema definition
- Wrap migration application + recording in transactions for atomicity
- Add config.yaml to .gitignore to prevent accidental secret commits
- Pin Dockerfile base image to alpine:3.21 and add non-root appuser
- Add test TestGetUnnotifiedReleases_IgnoredExcluded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 11:46:51 +03:00
parent c23b698f67
commit 5c2aadaab6
5 changed files with 64 additions and 11 deletions

View File

@@ -96,7 +96,7 @@ func (db *DB) migrate() error {
{
name: "003_create_notifications_sent",
sql: `CREATE TABLE IF NOT EXISTS notifications_sent (
rgid TEXT NOT NULL,
rgid TEXT NOT NULL REFERENCES external_releases(rgid),
sent_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (rgid, sent_at)
);`,
@@ -112,13 +112,24 @@ func (db *DB) migrate() error {
continue
}
if _, err := db.conn.Exec(m.sql); err != nil {
tx, err := db.conn.Begin()
if err != nil {
return fmt.Errorf("begin transaction for migration %s: %w", m.name, err)
}
if _, err := tx.Exec(m.sql); err != nil {
tx.Rollback()
return fmt.Errorf("apply migration %s: %w", m.name, err)
}
if err := db.markMigrationApplied(m.name); err != nil {
if _, err := tx.Exec("INSERT INTO _migrations (name) VALUES (?)", m.name); err != nil {
tx.Rollback()
return fmt.Errorf("record migration %s: %w", m.name, err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit migration %s: %w", m.name, err)
}
}
return nil
@@ -134,11 +145,6 @@ func (db *DB) isMigrationApplied(name string) (bool, error) {
return count > 0, nil
}
// markMigrationApplied records a migration as applied.
func (db *DB) markMigrationApplied(name string) error {
_, err := db.conn.Exec("INSERT INTO _migrations (name) VALUES (?)", name)
return err
}
// ArtistSettings represents a row in the artist_settings table.
type ArtistSettings struct {