refactor: inject data fix dependencies
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"mal/internal"
|
"mal/internal"
|
||||||
"mal/internal/config"
|
"mal/internal/config"
|
||||||
"mal/internal/database"
|
|
||||||
"mal/internal/db"
|
"mal/internal/db"
|
||||||
"mal/internal/observability"
|
"mal/internal/observability"
|
||||||
errlog "mal/pkg"
|
errlog "mal/pkg"
|
||||||
@@ -229,7 +228,7 @@ func updateAvatars(ctx context.Context, dbConn *sql.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runFixes(ctx context.Context, dbConn *sql.DB) {
|
func runFixes(ctx context.Context, dbConn *sql.DB) {
|
||||||
if err := database.RunMigrationsAndFixes(dbConn); err != nil {
|
if err := internal.RunMigrationsAndFixes(dbConn); err != nil {
|
||||||
observability.Error("cli_run_migrations_and_fixes_failed", "cmd/user", "", nil, err)
|
observability.Error("cli_run_migrations_and_fixes_failed", "cmd/user", "", nil, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"mal/internal/database"
|
||||||
|
dbfixes "mal/internal/database/fixes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultAvatarURL(username string) string {
|
func DefaultAvatarURL(username string) string {
|
||||||
@@ -10,3 +14,9 @@ func DefaultAvatarURL(username string) string {
|
|||||||
params.Set("seed", strings.TrimSpace(username))
|
params.Set("seed", strings.TrimSpace(username))
|
||||||
return "https://api.dicebear.com/9.x/dylan/svg?" + params.Encode()
|
return "https://api.dicebear.com/9.x/dylan/svg?" + params.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunMigrationsAndFixes(sqlDB *sql.DB) error {
|
||||||
|
return database.RunMigrationsAndFixes(sqlDB, dbfixes.Dependencies{
|
||||||
|
DefaultAvatarURL: DefaultAvatarURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mal/internal/config"
|
"mal/internal/config"
|
||||||
|
dbfixes "mal/internal/database/fixes"
|
||||||
"mal/internal/db"
|
"mal/internal/db"
|
||||||
"mal/internal/observability"
|
"mal/internal/observability"
|
||||||
|
|
||||||
@@ -21,7 +22,6 @@ var Module = fx.Options(
|
|||||||
ProvideSQLDB,
|
ProvideSQLDB,
|
||||||
ProvideQueries,
|
ProvideQueries,
|
||||||
),
|
),
|
||||||
fx.Invoke(RunMigrationsAndFixes),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideSQLDB(cfg config.Config) (*sql.DB, error) {
|
func ProvideSQLDB(cfg config.Config) (*sql.DB, error) {
|
||||||
@@ -58,11 +58,11 @@ func RunMigrations(sqlDB *sql.DB) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func RunMigrationsAndFixes(sqlDB *sql.DB) error {
|
func RunMigrationsAndFixes(sqlDB *sql.DB, deps dbfixes.Dependencies) error {
|
||||||
if err := RunMigrations(sqlDB); err != nil {
|
if err := RunMigrations(sqlDB); err != nil {
|
||||||
return fmt.Errorf("run migrations: %w", err)
|
return fmt.Errorf("run migrations: %w", err)
|
||||||
}
|
}
|
||||||
if err := RunDataFixes(sqlDB); err != nil {
|
if err := RunDataFixes(sqlDB, deps); err != nil {
|
||||||
return fmt.Errorf("run data fixes: %w", err)
|
return fmt.Errorf("run data fixes: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
errlog "mal/pkg"
|
errlog "mal/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunDataFixes(sqlDB *sql.DB) error {
|
func RunDataFixes(sqlDB *sql.DB, deps dbfixes.Dependencies) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func RunDataFixes(sqlDB *sql.DB) error {
|
|||||||
"id": fix.ID,
|
"id": fix.ID,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err := fix.Apply(ctx, sqlDB); err != nil {
|
if err := fix.Apply(ctx, sqlDB, deps); err != nil {
|
||||||
return fmt.Errorf("data fix %s failed: %w", fix.ID, err)
|
return fmt.Errorf("data fix %s failed: %w", fix.ID, err)
|
||||||
}
|
}
|
||||||
if err := markFixApplied(ctx, sqlDB, fix.ID); err != nil {
|
if err := markFixApplied(ctx, sqlDB, fix.ID); err != nil {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
Register(Fix{
|
Register(Fix{
|
||||||
ID: "20260526_episode_availability_backfill_next_refresh_at",
|
ID: "20260526_episode_availability_backfill_next_refresh_at",
|
||||||
Apply: func(ctx context.Context, sqlDB *sql.DB) error {
|
Apply: func(ctx context.Context, sqlDB *sql.DB, _ Dependencies) error {
|
||||||
// Old caches could have next_refresh_at NULL (especially for airing shows with missing broadcast metadata),
|
// Old caches could have next_refresh_at NULL (especially for airing shows with missing broadcast metadata),
|
||||||
// which can result in "never refresh again" behavior on the server.
|
// which can result in "never refresh again" behavior on the server.
|
||||||
_, err := sqlDB.ExecContext(ctx, `
|
_, err := sqlDB.ExecContext(ctx, `
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mal/internal"
|
|
||||||
errlog "mal/pkg"
|
errlog "mal/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Register(Fix{
|
Register(Fix{
|
||||||
ID: "20260528_backfill_avatar_url",
|
ID: "20260528_backfill_avatar_url",
|
||||||
Apply: func(ctx context.Context, sqlDB *sql.DB) error {
|
Apply: func(ctx context.Context, sqlDB *sql.DB, deps Dependencies) error {
|
||||||
|
if deps.DefaultAvatarURL == nil {
|
||||||
|
return fmt.Errorf("default avatar URL dependency is required")
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := sqlDB.QueryContext(ctx, `SELECT id, username FROM user WHERE avatar_url = ''`)
|
rows, err := sqlDB.QueryContext(ctx, `SELECT id, username FROM user WHERE avatar_url = ''`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("query users missing avatar_url: %w", err)
|
return fmt.Errorf("query users missing avatar_url: %w", err)
|
||||||
@@ -35,7 +38,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range toUpdate {
|
for _, u := range toUpdate {
|
||||||
avatarURL := internal.DefaultAvatarURL(u.username)
|
avatarURL := deps.DefaultAvatarURL(u.username)
|
||||||
if _, err := sqlDB.ExecContext(ctx, `UPDATE user SET avatar_url = ? WHERE id = ?`, avatarURL, u.id); err != nil {
|
if _, err := sqlDB.ExecContext(ctx, `UPDATE user SET avatar_url = ? WHERE id = ?`, avatarURL, u.id); err != nil {
|
||||||
return fmt.Errorf("update avatar_url for user %s: %w", u.id, err)
|
return fmt.Errorf("update avatar_url for user %s: %w", u.id, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ type animeDurationRow struct {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Register(Fix{
|
Register(Fix{
|
||||||
ID: "20260608_backfill_anime_duration_seconds",
|
ID: "20260608_backfill_anime_duration_seconds",
|
||||||
Apply: applyAnimeDurationSecondsBackfill,
|
Apply: func(ctx context.Context, sqlDB *sql.DB, _ Dependencies) error {
|
||||||
|
return applyAnimeDurationSecondsBackfill(ctx, sqlDB)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import (
|
|||||||
|
|
||||||
type Fix struct {
|
type Fix struct {
|
||||||
ID string
|
ID string
|
||||||
Apply func(ctx context.Context, sqlDB *sql.DB) error
|
Apply func(ctx context.Context, sqlDB *sql.DB, deps Dependencies) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dependencies struct {
|
||||||
|
DefaultAvatarURL func(username string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
var registered []Fix
|
var registered []Fix
|
||||||
|
|||||||
Reference in New Issue
Block a user