diff --git a/.env.bak.1785817103 b/.env.bak.1785817103 new file mode 100644 index 0000000..a646009 --- /dev/null +++ b/.env.bak.1785817103 @@ -0,0 +1,17 @@ +# --- worst-scan-web --- + +# Backend API (pipeline) +API_BASE_URL=http://194.163.191.200:8080/api/v1 +API_KEY= + +# Web auth (vacío = sin login) +WEB_PASSWORD= + +# Webhook validation +WEBHOOK_SECRET= + +# DB path +DB_PATH=/home/ren/web/worst-scan-web/data/worst-scan.db + +# Covers dir +COVERS_DIR=/home/ren/web/worst-scan-web/data/covers diff --git a/src/app/(app)/feed/page.tsx b/src/app/(app)/feed/page.tsx index c72524b..f2c7c44 100644 --- a/src/app/(app)/feed/page.tsx +++ b/src/app/(app)/feed/page.tsx @@ -4,30 +4,63 @@ import { useState } from "react" import { useGalleries } from "@/hooks/use-galleries" import { GalleryGrid } from "@/components/gallery-grid" import { FilterBar } from "@/components/filter-bar" +import type { GalleryItem } from "@/lib/types" export default function FeedPage() { const [status, setStatus] = useState("completed") - const { galleries, isLoading, mutate } = useGalleries(status, 1) + const [page, setPage] = useState(1) + const [all, setAll] = useState([]) + const { galleries, meta, isLoading, mutate } = useGalleries(status, page) + + // Acumula galleries de la página actual + las páginas anteriores (dedup por gid). + const items: GalleryItem[] = [] + const seen = new Set() + for (const g of all) { + if (!seen.has(g.gid)) { + seen.add(g.gid) + items.push(g) + } + } + for (const g of galleries) { + if (!seen.has(g.gid)) { + seen.add(g.gid) + items.push(g) + } + } + + const total = meta?.total ?? 0 + const hasMore = items.length < total + + const handleStatus = (next: string) => { + setStatus(next) + setPage(1) + setAll([]) + } + + const loadMore = () => { + setAll(items) + setPage((p) => p + 1) + } return (

Feed

- {isLoading && galleries.length === 0 + {isLoading && items.length === 0 ? "Cargando galleries..." - : `${galleries.length} galleries`} + : `${items.length}${total > items.length ? " de " + total : ""} galleries`}

mutate()} loading={isLoading} /> - {isLoading && galleries.length === 0 ? ( + {isLoading && items.length === 0 ? (
{Array.from({ length: 6 }).map((_, i) => (
@@ -41,7 +74,20 @@ export default function FeedPage() { ))}
) : ( - + <> + + {hasMore && ( +
+ +
+ )} + )}
) diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 46dc17b..233a4db 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -1,10 +1,24 @@ +import { cookies } from "next/headers" import { Sidebar } from "@/components/layout/sidebar" +import { verifySession } from "@/lib/auth" + +export default async function AppLayout({ children }: { children: React.ReactNode }) { + // La sidebar es el panel admin: solo visible con sesión válida. + // Los visitantes anónimos (que leen mangas públicos) no la ven. + let isAuthed = false + try { + const cookieStore = await cookies() + const session = cookieStore.get("session")?.value + if (session) { + const payload = await verifySession(session) + isAuthed = !!payload?.authenticated + } + } catch {} -export default function AppLayout({ children }: { children: React.ReactNode }) { return (
- -
+ {isAuthed && } +
{children}
diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx index ca88410..237bffa 100644 --- a/src/app/(public)/layout.tsx +++ b/src/app/(public)/layout.tsx @@ -1,6 +1,8 @@ import Link from "next/link" -import { Image, Search } from "lucide-react" +import { Image } from "lucide-react" +// Header del sitio público: sin links al panel admin (que queda escondido). +// Solo navegación pública (logo -> home). export default function PublicLayout({ children }: { children: React.ReactNode }) { return ( <> @@ -12,15 +14,7 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
worst-scan - + diff --git a/src/app/(public)/p/[slug]/page.tsx b/src/app/(public)/p/[slug]/page.tsx index be57a51..e17a246 100644 --- a/src/app/(public)/p/[slug]/page.tsx +++ b/src/app/(public)/p/[slug]/page.tsx @@ -176,4 +176,4 @@ export default async function PostDetailPage({ )} ) -} +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 831f2ef..ae899ed 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -21,12 +21,28 @@ const publicPrefixes = [ ] // Páginas públicas del sitio (server-side, sin login). -const publicPages = ["/", "/p/", "/tag/"] +// "/" es MATCH EXACTO (startsWith("/") matchearía TODO). +// "/gallery/" incluye el lector: cualquiera puede LEER (GET); las acciones +// del panel (eliminar, etc.) quedan protegidas por el middleware vía método. +const publicPages = ["/p/", "/tag/", "/gallery/"] + +// GET de lectura del proxy hacia el pipeline: públicos para que el lector +// funcione sin login. POST/DELETE/PATCH (mutaciones) siempre requieren sesión. +function isPublicProxyRead(pathname: string, method: string): boolean { + if (method !== "GET" && method !== "HEAD") return false + return ( + pathname.startsWith("/api/proxy/galleries/") || + pathname === "/api/proxy/status" || + pathname === "/api/proxy/queue" + ) +} function isPublicPath(pathname: string, method: string): boolean { + if (pathname === "/") return true if (publicExact.has(pathname)) return true if (publicPrefixes.some((p) => pathname.startsWith(p))) return true if (publicPages.some((p) => pathname.startsWith(p))) return true + if (isPublicProxyRead(pathname, method)) return true // /api/posts: GET de listado público; mutaciones requieren sesión. if (pathname === "/api/posts" && method === "GET") return true