cleanup: remove redundant and old architectural files

This commit is contained in:
2026-05-13 10:34:16 +02:00
parent 0d6c7613a9
commit ab31cf4c4c
27 changed files with 0 additions and 5954 deletions

View File

@@ -1,118 +0,0 @@
package db
import (
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
// RunMigrations applies all *.sql files in migrationsDir in sorted order,
// skipping any already recorded in migration_version.
func RunMigrations(db *sql.DB, migrationsDir string) error {
if migrationsDir == "" {
return fmt.Errorf("migrations directory is required")
}
// Create migration tracking table
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS migration_version (
name TEXT PRIMARY KEY,
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return err
}
migrations, err := filepath.Glob(filepath.Join(migrationsDir, "*.sql"))
if err != nil {
return err
}
if len(migrations) == 0 {
return fmt.Errorf("no migration files found in %s", migrationsDir)
}
sort.Strings(migrations)
appliedNames, err := loadAppliedMigrationNames(db)
if err != nil {
return err
}
for _, migrationFile := range migrations {
migrationName := filepath.Base(migrationFile)
if migrationApplied(appliedNames, migrationName) {
continue // already applied
}
migrationSQL, err := os.ReadFile(migrationFile)
if err != nil {
return err
}
if _, err := db.Exec(string(migrationSQL)); err != nil {
return err // stop on first failure
}
// record applied migration
_, err = db.Exec("INSERT INTO migration_version (name) VALUES (?)", migrationName)
if err != nil {
return err
}
appliedNames[migrationName] = struct{}{}
log.Printf("migration %s applied successfully", migrationName)
}
return nil
}
func loadAppliedMigrationNames(db *sql.DB) (map[string]struct{}, error) {
rows, err := db.Query("SELECT name FROM migration_version")
if err != nil {
return nil, err
}
defer func() { _ = rows.Close() }()
applied := make(map[string]struct{})
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
applied[name] = struct{}{}
}
if err := rows.Err(); err != nil {
return nil, err
}
return applied, nil
}
// migrationApplied checks the applied names map for a match,
// including legacy paths and case-insensitive basename matches.
func migrationApplied(appliedNames map[string]struct{}, migrationName string) bool {
if _, exists := appliedNames[migrationName]; exists {
return true
}
legacyName := filepath.ToSlash(filepath.Join("migrations", migrationName))
if _, exists := appliedNames[legacyName]; exists {
return true
}
for appliedName := range appliedNames {
if strings.EqualFold(filepath.Base(appliedName), migrationName) {
return true
}
}
return false
}

View File

@@ -1,162 +0,0 @@
package server
import (
"database/sql"
"fmt"
"net/http"
"path/filepath"
"strings"
"time"
"mal/api/anime"
"mal/api/auth"
"mal/api/playback"
"mal/api/watchlist"
"mal/integrations/jikan"
"mal/internal/db"
"mal/internal/middleware"
pkgmiddleware "mal/pkg/middleware"
)
type Config struct {
DB *db.Queries
SQLDB *sql.DB
JikanClient *jikan.Client
AuthService *auth.Service
AuthLimiter *pkgmiddleware.Limiter
PlaybackProxySecret string
}
// withMimeTypes sets Content-Type for common static asset extensions
func withMimeTypes(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ext := strings.ToLower(filepath.Ext(r.URL.Path))
switch ext {
case ".js":
w.Header().Set("Content-Type", "application/javascript")
case ".css":
w.Header().Set("Content-Type", "text/css")
case ".svg":
w.Header().Set("Content-Type", "image/svg+xml")
case ".json":
w.Header().Set("Content-Type", "application/json")
}
next.ServeHTTP(w, r)
})
}
// noCache sends headers to prevent caching of dynamic/static assets
func noCache(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
next.ServeHTTP(w, r)
})
}
// NewAuthLimiter returns a rate limiter for auth endpoints: 5 attempts per minute
func NewAuthLimiter() *pkgmiddleware.Limiter {
return pkgmiddleware.NewLimiter(pkgmiddleware.Config{
MaxAttempts: 5,
Window: time.Minute,
})
}
// NewRouter wires up all HTTP handlers and middleware.
// Auth is enforced globally; public routes must opt-out via middleware policy.
func NewRouter(cfg Config) http.Handler {
mux := http.NewServeMux()
authHandler := auth.NewHandler(cfg.AuthService)
watchlistSvc := watchlist.NewService(cfg.DB, cfg.SQLDB, cfg.JikanClient)
watchlistHandler := watchlist.NewHandler(watchlistSvc)
animeSvc := anime.NewService(cfg.JikanClient, cfg.DB)
animeHandler := anime.NewHandler(animeSvc)
playbackSvc, err := playback.NewService(cfg.DB, cfg.SQLDB, playback.Config{
ProxyTokenSecret: cfg.PlaybackProxySecret,
})
if err != nil {
panic(fmt.Sprintf("failed to initialize playback service: %v", err))
}
playbackHandler := playback.NewHandler(playbackSvc, cfg.JikanClient)
// Serve static files with no-cache headers
fs := noCache(http.FileServer(http.Dir("./static")))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
// Serve built frontend assets with no-cache headers
dist := noCache(http.FileServer(http.Dir("./dist")))
mux.Handle("/dist/", http.StripPrefix("/dist/", withMimeTypes(dist)))
// Serve Apple Touch Icons from static directory
mux.HandleFunc("/apple-touch-icon.png", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, "./static/apple-touch-icon.svg")
})
mux.HandleFunc("/apple-touch-icon-precomposed.png", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, "./static/apple-touch-icon-precomposed.svg")
})
mux.HandleFunc("/apple-touch-icon-120x120.png", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, "./static/apple-touch-icon-120x120.svg")
})
mux.HandleFunc("/apple-touch-icon-120x120-precomposed.png", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
http.ServeFile(w, r, "./static/apple-touch-icon-120x120-precomposed.svg")
})
mux.HandleFunc("/", animeHandler.HandleCatalog)
mux.HandleFunc("/api/catalog/airing", animeHandler.HandleCatalogAiring)
mux.HandleFunc("/api/catalog/popular", animeHandler.HandleCatalogPopular)
mux.HandleFunc("/api/catalog/continue", animeHandler.HandleCatalogContinue)
mux.HandleFunc("/search", animeHandler.HandleSearch)
mux.HandleFunc("/browse", animeHandler.HandleBrowse)
mux.HandleFunc("/discover", animeHandler.HandleDiscover)
mux.HandleFunc("/api/discover/trending", animeHandler.HandleDiscoverTrending)
mux.HandleFunc("/api/discover/upcoming", animeHandler.HandleDiscoverUpcoming)
mux.HandleFunc("/api/discover/top", animeHandler.HandleDiscoverTop)
mux.HandleFunc("/api/search-quick", animeHandler.HandleQuickSearch)
mux.HandleFunc("/api/jikan/random/anime", animeHandler.HandleRandomAnime)
mux.HandleFunc("/anime/", func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/watch") {
playbackHandler.HandleWatchPage(w, r)
return
}
animeHandler.HandleAnimeDetails(w, r)
})
mux.HandleFunc("/api/watch-order", animeHandler.HandleHTMLWatchOrder)
mux.HandleFunc("/watch/", playbackHandler.HandleWatchPage)
mux.HandleFunc("/watch/proxy/stream", playbackHandler.HandleProxy)
mux.HandleFunc("/watch/proxy/segment", playbackHandler.HandleProxy)
mux.HandleFunc("/watch/proxy/subtitle", playbackHandler.HandleProxy)
mux.HandleFunc("/api/watch-progress", playbackHandler.HandleSaveProgress)
mux.HandleFunc("/api/watch-complete", playbackHandler.HandleCompleteAnime)
mux.HandleFunc("/api/watch/episode/", playbackHandler.HandleEpisodeData)
mux.HandleFunc("/api/watch/thumbnails/", playbackHandler.HandleEpisodeThumbnails)
// Auth Endpoints
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
authHandler.HandleLoginPage(w, r)
} else {
cfg.AuthLimiter.AuthMiddleware(pkgmiddleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleLogin))).ServeHTTP(w, r)
}
})
mux.HandleFunc("/logout", authHandler.HandleLogout)
// Watchlist Endpoints
mux.HandleFunc("/api/watchlist", watchlistHandler.HandleUpdateWatchlist)
mux.HandleFunc("/api/watchlist/", watchlistHandler.HandleDeleteWatchlist)
mux.HandleFunc("/api/continue-watching/", watchlistHandler.HandleDeleteContinueWatching)
mux.HandleFunc("/watchlist", watchlistHandler.HandleGetWatchlist)
// Wrap mux with global CSRF origin verification and auth checking
protectedHandler := middleware.RequireGlobalAuthWithPolicy(middleware.NewAccessPolicy())(pkgmiddleware.VerifyOrigin(mux))
authenticatedHandler := middleware.Auth(cfg.AuthService)(protectedHandler)
return pkgmiddleware.RequestLogger(authenticatedHandler)
}