refactor: reorganize project structure following go standards
This commit is contained in:
@@ -9,10 +9,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/jikan"
|
||||
"mal/internal/shared/middleware"
|
||||
"mal/internal/templates"
|
||||
"mal/internal/db"
|
||||
"mal/integrations/jikan"
|
||||
"mal/internal/middleware"
|
||||
"mal/web/templates"
|
||||
)
|
||||
|
||||
func deduplicateAnimes(animes []jikan.Anime) []jikan.Anime {
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -3,7 +3,7 @@ package auth
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"mal/internal/templates"
|
||||
"mal/web/templates"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/jikan"
|
||||
"mal/internal/shared/middleware"
|
||||
"mal/internal/templates"
|
||||
"mal/internal/db"
|
||||
"mal/integrations/jikan"
|
||||
"mal/internal/middleware"
|
||||
"mal/web/templates"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
func (s *Service) SaveProgress(ctx context.Context, userID string, animeID int64, episode int, timeSeconds float64, animeSeed *database.UpsertAnimeParams) error {
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
func TestNormalizeProxyURLRejectsLocalhost(t *testing.T) {
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/shared/middleware"
|
||||
"mal/internal/templates"
|
||||
"mal/internal/db"
|
||||
"mal/internal/middleware"
|
||||
"mal/web/templates"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
type fakeQuerier struct {
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/features/auth"
|
||||
"mal/internal/jikan"
|
||||
dbpkg "mal/internal/db"
|
||||
"mal/api/auth"
|
||||
"mal/integrations/jikan"
|
||||
"mal/internal/server"
|
||||
"mal/internal/shared/middleware"
|
||||
"mal/pkg/middleware"
|
||||
"mal/internal/worker"
|
||||
)
|
||||
|
||||
@@ -30,11 +30,11 @@ func main() {
|
||||
defer db.Close()
|
||||
|
||||
migrationsDir := migrationsDir()
|
||||
if err := database.RunMigrations(db, migrationsDir); err != nil {
|
||||
if err := dbpkg.RunMigrations(db, migrationsDir); err != nil {
|
||||
log.Fatalf("failed to run migrations: %v", err)
|
||||
}
|
||||
|
||||
queries := database.New(db)
|
||||
queries := dbpkg.New(db)
|
||||
jikanClient := jikan.NewClient(queries)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mal/internal/watchorder"
|
||||
"mal/integrations/watchorder"
|
||||
)
|
||||
|
||||
const chiakiWatchOrderURL = "https://chiaki.site/?/tools/watch_order/id/%d"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
type staleCacheQuerier struct {
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/db"
|
||||
)
|
||||
|
||||
type AccessPolicy struct {
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/features/auth"
|
||||
"mal/internal/db"
|
||||
"mal/api/auth"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
@@ -4,13 +4,14 @@ import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/features/anime"
|
||||
"mal/internal/features/auth"
|
||||
"mal/internal/features/playback"
|
||||
"mal/internal/features/watchlist"
|
||||
"mal/internal/jikan"
|
||||
"mal/internal/shared/middleware"
|
||||
"mal/internal/db"
|
||||
"mal/api/anime"
|
||||
"mal/api/auth"
|
||||
"mal/api/playback"
|
||||
"mal/api/watchlist"
|
||||
"mal/integrations/jikan"
|
||||
"mal/internal/middleware"
|
||||
pkgmiddleware "mal/pkg/middleware"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -70,7 +71,7 @@ func NewRouter(cfg Config) http.Handler {
|
||||
if r.Method == http.MethodGet {
|
||||
authHandler.HandleLoginPage(w, r)
|
||||
} else {
|
||||
middleware.RateLimitAuth(middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleLogin))).ServeHTTP(w, r)
|
||||
pkgmiddleware.RateLimitAuth(pkgmiddleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleLogin))).ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -84,7 +85,7 @@ func NewRouter(cfg Config) http.Handler {
|
||||
|
||||
// Wrap mux with global CSRF origin verification and auth checking,
|
||||
// THEN auth context parsing.
|
||||
protectedHandler := middleware.RequireGlobalAuthWithPolicy(middleware.NewAccessPolicy())(middleware.VerifyOrigin(mux))
|
||||
protectedHandler := middleware.RequireGlobalAuthWithPolicy(middleware.NewAccessPolicy())(pkgmiddleware.VerifyOrigin(mux))
|
||||
authenticatedHandler := middleware.Auth(cfg.AuthService)(protectedHandler)
|
||||
return middleware.RequestLogger(authenticatedHandler)
|
||||
return pkgmiddleware.RequestLogger(authenticatedHandler)
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"mal/internal/database"
|
||||
)
|
||||
|
||||
func TestAccessPolicy_IsPublicPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := NewAccessPolicy()
|
||||
|
||||
if !policy.IsPublicPath("/") {
|
||||
t.Fatal("expected / to be public")
|
||||
}
|
||||
|
||||
if !policy.IsPublicPath("/api/search") {
|
||||
t.Fatal("expected /api/search to be public")
|
||||
}
|
||||
|
||||
if !policy.IsPublicPath("/static/app.css") {
|
||||
t.Fatal("expected /static/app.css to be public")
|
||||
}
|
||||
|
||||
if policy.IsPublicPath("/watchlist") {
|
||||
t.Fatal("expected /watchlist to be private")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireGlobalAuthWithPolicy_ProtectedPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := AccessPolicy{
|
||||
PublicPaths: map[string]struct{}{"/public": {}},
|
||||
}
|
||||
|
||||
h := RequireGlobalAuthWithPolicy(policy)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/private", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected redirect status, got %d", rec.Code)
|
||||
}
|
||||
|
||||
if location := rec.Header().Get("Location"); location != "/login" {
|
||||
t.Fatalf("expected redirect to /login, got %q", location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireGlobalAuthWithPolicy_AllowsAuthenticatedUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := AccessPolicy{
|
||||
PublicPaths: map[string]struct{}{},
|
||||
}
|
||||
|
||||
h := RequireGlobalAuthWithPolicy(policy)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/private", nil)
|
||||
ctx := context.WithValue(req.Context(), UserContextKey, &database.User{ID: "user-1"})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rec, req.WithContext(ctx))
|
||||
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusNoContent, rec.Code)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"mal/internal/database"
|
||||
)
|
||||
|
||||
func TestRequireAuth_UnauthenticatedAPIRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/watchlist", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, rec.Code)
|
||||
}
|
||||
|
||||
if got := rec.Header().Get("HX-Redirect"); got != "/login" {
|
||||
t.Fatalf("expected HX-Redirect /login, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireAuth_AuthenticatedRequestPassesThrough(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/watchlist", nil)
|
||||
ctx := context.WithValue(req.Context(), UserContextKey, &database.User{ID: "user-1"})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rec, req.WithContext(ctx))
|
||||
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusNoContent, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireGlobalAuth_AllowsPublicRoute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := RequireGlobalAuthWithPolicy(NewAccessPolicy())(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"mal/internal/database"
|
||||
"mal/internal/jikan"
|
||||
"mal/internal/db"
|
||||
"mal/integrations/jikan"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
|
||||
Reference in New Issue
Block a user