Add shared test functions in store_test.go covering all Store interface
operations (users, tokens, access control, tiers, billing, stats, etc.)
with per-backend wrappers in store_sqlite_test.go and store_postgres_test.go
following the webpush test pattern.
Fix broken isUniqueConstraintError() which used incorrect interface
assertions instead of string matching for SQLite/PostgreSQL errors.
Extract database operations from Manager into a Store interface with
SQLite and PostgreSQL implementations using a shared commonStore.
Split SQLite migrations into store_sqlite_migrations.go, use shared
schema_version table for PostgreSQL, rename user_user/user_tier tables
to "user"/tier, and wire up database-url in CLI commands.
Consolidate SQLite and Postgres store implementations into a single
commonStore with database-specific SQL queries passed via configuration.
This eliminates ~100 lines of duplicate code while maintaining full
functionality for both backends.
Key changes:
- Move all store methods to commonStore in store.go
- Remove sqliteStore and postgresStore wrapper structs
- Refactor SQLite to use QueryRow() pattern like Postgres
- Pass database-specific queries via storeQueries struct
- Make store types unexported, only expose Store interface
All tests pass for both SQLite and PostgreSQL backends.
- Add SetSubscriptionUpdatedAt to Store interface, remove DB() accessor
- Rename store files to store_sqlite.go and store_postgres.go
- Use camelCase for test function names
- Add tests for upsert field updates and multi-user removal
- Use transaction in setupNewPostgresDB
- Use lowercase "excluded." in PostgreSQL upsert query
Share test logic in store_test.go with thin per-backend wrappers.
Add SetSubscriptionUpdatedAt to both stores, removing the need for
raw SQL and the DB() accessor in tests.
Replace wlock.TryLock() with a proper Lock() + closed flag to prevent
writing to a response writer that has been cleaned up after the handler
returns. The previous TryLock approach could not guarantee the response
writer was still valid when a concurrent Publish goroutine called Flush.