diff --git a/backend/package.json b/backend/package.json index f08fd13..f45b104 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,42 +13,30 @@ "dependencies": { "@2toad/profanity": "^3.3.0", "@auth/prisma-adapter": "2.11.1", - "@bprogress/next": "^3.2.12", - "@hello-pangea/dnd": "^18.0.1", "@prisma/client": "^6.19.2", "bit-buffer": "^0.3.0", - "canvas-confetti": "^1.9.4", "dayjs": "^1.11.20", "downshift": "^9.3.2", - "embla-carousel-react": "^8.6.0", "file-type": "^22.0.1", - "jsqr": "^1.4.0", "next": "16.2.3", "next-auth": "5.0.0-beta.30", "qrcode-generator": "^2.0.4", "react": "^19.2.5", "react-dom": "^19.2.5", - "react-dropzone": "^15.0.0", - "react-image-crop": "^11.0.10", "redis": "^5.11.0", "satori": "^0.26.0", - "seedrandom": "^3.0.5", "sharp": "^0.34.5", "sjcl-with-all": "1.0.8", - "swr": "^2.4.1", "zod": "^4.3.6", - "@tomodachi-share/shared": "workspace:*" - + "@tomodachi-share/shared": "workspace:*" }, "devDependencies": { "@eslint/eslintrc": "^3.3.5", "@iconify/react": "^6.0.2", "@tailwindcss/postcss": "^4.2.2", - "@types/canvas-confetti": "^1.9.0", "@types/node": "^25.6.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@types/seedrandom": "^3.0.8", "@types/sjcl": "^1.0.34", "eslint": "^10.2.0", "eslint-config-next": "16.2.3", diff --git a/backend/src/app/api/mii/list/route.ts b/backend/src/app/api/mii/list/route.ts index f3293a4..d3b0465 100644 --- a/backend/src/app/api/mii/list/route.ts +++ b/backend/src/app/api/mii/list/route.ts @@ -2,10 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { auth } from "@/lib/auth"; import { searchSchema } from "@tomodachi-share/shared/schemas"; -import { RateLimit } from "@/lib/rate-limit"; import { Prisma } from "@prisma/client"; -import crypto from "crypto"; -import seedrandom from "seedrandom"; export async function GET(request: NextRequest) { const session = await auth(); @@ -94,75 +91,37 @@ export async function GET(request: NextRequest) { }, }; - const skip = (page - 1) * limit; - let totalCount: number; - let filteredCount: number; let miis: Prisma.MiiGetPayload<{ select: typeof select }>[]; - if (sort === "random") { - // Get all IDs that match the where conditions - const matchingIds = await prisma.mii.findMany({ - where, - select: { id: true }, - }); + // Sorting by likes, newest, or oldest + let orderBy: Prisma.MiiOrderByWithRelationInput[]; - totalCount = matchingIds.length; - filteredCount = Math.max(0, Math.min(limit, totalCount - skip)); - - if (matchingIds.length === 0) return; - - // Use seed for consistent random results - const randomSeed = seed || crypto.randomInt(0, 1_000_000_000); - const rng = seedrandom(randomSeed.toString()); - - // Randomize all IDs using the Durstenfeld algorithm - for (let i = matchingIds.length - 1; i > 0; i--) { - const j = Math.floor(rng() * (i + 1)); - [matchingIds[i], matchingIds[j]] = [matchingIds[j], matchingIds[i]]; - } - - // Convert to number[] array - const selectedIds = matchingIds.slice(skip, skip + limit).map((i) => i.id); - - miis = await prisma.mii.findMany({ - where: { - id: { in: selectedIds }, - }, - select, - }); + if (sort === "likes") { + orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }]; + } else if (sort === "oldest") { + orderBy = [{ createdAt: "asc" }, { name: "asc" }]; } else { - // Sorting by likes, newest, or oldest - let orderBy: Prisma.MiiOrderByWithRelationInput[]; - - if (sort === "likes") { - orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }]; - } else if (sort === "oldest") { - orderBy = [{ createdAt: "asc" }, { name: "asc" }]; - } else { - // default to newest - orderBy = [{ createdAt: "desc" }, { name: "asc" }]; - } - - [totalCount, filteredCount, miis] = await Promise.all([ - prisma.mii.count({ where: { ...where } }), // TODO: User id - prisma.mii.count({ where, skip, take: limit }), - prisma.mii.findMany({ - where, - orderBy, - select, - skip: (page - 1) * limit, - take: limit, - }), - ]); + // default to newest + orderBy = [{ createdAt: "desc" }, { name: "asc" }]; } + [totalCount, miis] = await Promise.all([ + prisma.mii.count({ where: { ...where } }), // TODO: User id + prisma.mii.findMany({ + where, + orderBy, + select, + skip: (page - 1) * limit, + take: limit, + }), + ]); + const lastPage = Math.ceil(totalCount / limit); return NextResponse.json({ miis, totalCount, - filteredCount, lastPage, }); } diff --git a/backend/src/lib/auth.ts b/backend/src/lib/auth.ts index 08da09d..9a04006 100644 --- a/backend/src/lib/auth.ts +++ b/backend/src/lib/auth.ts @@ -43,7 +43,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ }, async redirect({ url, baseUrl }) { - return process.env.FRONTEND_URL ?? "http://localhost:4321"; + return process.env.NEXT_PUBLIC_FRONTEND_URL ?? "http://localhost:4321"; }, }, }); diff --git a/frontend/package.json b/frontend/package.json index e1c82fd..6905b0e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,6 @@ "react-dropzone": "^15.0.0", "react-image-crop": "^11.0.10", "react-router": "^7.14.1", - "seedrandom": "^3.0.5", "tailwindcss": "^4.2.2", "zod": "^4.3.6" }, diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index 19b8020..72a2ba0 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -31,8 +31,7 @@ export default function Header() {
  • - {" "} - Submit{" "} + Submit
  • diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 23cc42b..fcaac8f 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { Suspense, useEffect, useState } from "react"; +import { Suspense, useEffect, useMemo, useState } from "react"; import FilterMenu from "../components/mii/list/filter-menu"; import SortSelect from "../components/mii/list/sort-select"; import MiiGrid from "../components/mii/list/mii-grid"; @@ -7,13 +7,12 @@ import Skeleton from "../components/mii/list/skeleton"; interface ApiResponse { totalCount: number; - filteredCount: number; miis: any[]; lastPage: number; } export default function IndexPage() { - const searchParams = new URLSearchParams(location.search); + const searchParams = useMemo(() => new URLSearchParams(location.search), []); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -31,7 +30,7 @@ export default function IndexPage() { console.error(err); setLoading(false); }); - }, []); + }, [searchParams]); return ( <> @@ -46,19 +45,8 @@ export default function IndexPage() {
    - {data.totalCount == data.filteredCount ? ( - <> - {data.totalCount} - {data.totalCount === 1 ? "Mii" : "Miis"} - - ) : ( - <> - {data.filteredCount} - of - {data.totalCount} - Miis - - )} + {data.totalCount} + {data.totalCount === 1 ? "Mii" : "Miis"}
    diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 69836f2..29d838e 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,6 +1,14 @@ import { Icon } from "@iconify/react"; +import { useStore } from "@nanostores/react"; +import { useNavigate } from "react-router"; +import { session } from "../session"; export default function LoginPage() { + const navigate = useNavigate(); + const $session = useStore(session); + + if ($session) navigate("/"); + const API_URL = import.meta.env.VITE_API_URL; return ( diff --git a/frontend/src/pages/submit.tsx b/frontend/src/pages/submit.tsx index 7365afa..95926ab 100644 --- a/frontend/src/pages/submit.tsx +++ b/frontend/src/pages/submit.tsx @@ -1,5 +1,12 @@ -import SubmitForm from "../components/submit-form"; - -export default function SubmitPage() { - return ; -} \ No newline at end of file +import { useStore } from "@nanostores/react"; +import SubmitForm from "../components/submit-form"; +import { session } from "../session"; +import { useNavigate } from "react-router"; + +export default function SubmitPage() { + const navigate = useNavigate(); + const $session = useStore(session); + + if (!$session) navigate("/login"); + return ; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee9035a..89db52f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,12 +16,6 @@ importers: '@auth/prisma-adapter': specifier: 2.11.1 version: 2.11.1(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.2))(typescript@6.0.2)) - '@bprogress/next': - specifier: ^3.2.12 - version: 3.2.12(next@16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@hello-pangea/dnd': - specifier: ^18.0.1 - version: 18.0.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@prisma/client': specifier: ^6.19.2 version: 6.19.3(prisma@6.19.3(typescript@6.0.2))(typescript@6.0.2) @@ -31,24 +25,15 @@ importers: bit-buffer: specifier: ^0.3.0 version: 0.3.0 - canvas-confetti: - specifier: ^1.9.4 - version: 1.9.4 dayjs: specifier: ^1.11.20 version: 1.11.20 downshift: specifier: ^9.3.2 version: 9.3.2(react@19.2.5) - embla-carousel-react: - specifier: ^8.6.0 - version: 8.6.0(react@19.2.5) file-type: specifier: ^22.0.1 version: 22.0.1 - jsqr: - specifier: ^1.4.0 - version: 1.4.0 next: specifier: 16.2.3 version: 16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -64,30 +49,18 @@ importers: react-dom: specifier: ^19.2.5 version: 19.2.5(react@19.2.5) - react-dropzone: - specifier: ^15.0.0 - version: 15.0.0(react@19.2.5) - react-image-crop: - specifier: ^11.0.10 - version: 11.0.10(react@19.2.5) redis: specifier: ^5.11.0 version: 5.12.1 satori: specifier: ^0.26.0 version: 0.26.0 - seedrandom: - specifier: ^3.0.5 - version: 3.0.5 sharp: specifier: ^0.34.5 version: 0.34.5 sjcl-with-all: specifier: 1.0.8 version: 1.0.8 - swr: - specifier: ^2.4.1 - version: 2.4.1(react@19.2.5) zod: specifier: ^4.3.6 version: 4.3.6 @@ -101,9 +74,6 @@ importers: '@tailwindcss/postcss': specifier: ^4.2.2 version: 4.2.2 - '@types/canvas-confetti': - specifier: ^1.9.0 - version: 1.9.0 '@types/node': specifier: ^25.6.0 version: 25.6.0 @@ -113,9 +83,6 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) - '@types/seedrandom': - specifier: ^3.0.8 - version: 3.0.8 '@types/sjcl': specifier: ^1.0.34 version: 1.0.34 @@ -200,9 +167,6 @@ importers: react-router: specifier: ^7.14.1 version: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - seedrandom: - specifier: ^3.0.5 - version: 3.0.5 tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -392,13 +356,6 @@ packages: '@bprogress/core@1.3.4': resolution: {integrity: sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==} - '@bprogress/next@3.2.12': - resolution: {integrity: sha512-/ZvNwbAd0ty9QiQwCfT2AfwWVdAaEyCPx5RUz3CfiiJS/OLBohhDz/IC/srhwK9GnXeXavvtiUrpKzN5GJDwlw==} - peerDependencies: - next: '>=13.0.0' - react: '>=18.0.0' - react-dom: '>=18.0.0' - '@bprogress/react@1.2.7': resolution: {integrity: sha512-MqJfHW+R5CQeWqyqrLxUjdBRHk24Xl63OkBLo5DMWqUqocUikRTfCIc/jtQQbPk7BRfdr5OP3Lx7YlfQ9QOZMQ==} peerDependencies: @@ -1717,10 +1674,6 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -2801,9 +2754,6 @@ packages: schema-dts@2.0.0: resolution: {integrity: sha512-t7NoCy3Rn5GHGx6p7s1qIYK/AeIb8ZxJNR9WUNFkwMv2CiiGZBmqqYWc2FlZVm5ZbiHMY4OvBWhj7QtyrFO2Jw==} - seedrandom@3.0.5: - resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2936,11 +2886,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.4.1: - resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - tailwindcss@4.2.2: resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} @@ -3277,14 +3222,6 @@ snapshots: '@bprogress/core@1.3.4': {} - '@bprogress/next@3.2.12(next@16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@bprogress/core': 1.3.4 - '@bprogress/react': 1.2.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - next: 16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - '@bprogress/react@1.2.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@bprogress/core': 1.3.4 @@ -4429,8 +4366,6 @@ snapshots: defu@6.1.7: {} - dequal@2.0.3: {} - destr@2.0.5: {} detect-libc@2.1.2: {} @@ -5722,8 +5657,6 @@ snapshots: transitivePeerDependencies: - typescript - seedrandom@3.0.5: {} - semver@6.3.1: {} semver@7.7.4: {} @@ -5908,12 +5841,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.4.1(react@19.2.5): - dependencies: - dequal: 2.0.3 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) - tailwindcss@4.2.2: {} tapable@2.3.2: {}