diff --git a/.env.example b/.env.example index 008b1ed..781021a 100644 --- a/.env.example +++ b/.env.example @@ -16,11 +16,7 @@ CLERK_SECRET_KEY="" CLERK_WEBHOOK_SIGNING_SECRET="" # Ably -ABLY_PRIVATE_KEY="" -NEXT_PUBLIC_ABLY_PUBLIC_KEY="" - -# Email -RESEND_API_KEY="" +ABLY_API_KEY="" # Unkey UNKEY_ROOT_KEY="" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 2971a0b..5880532 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ yarn-error.log* # typescript *.tsbuildinfo + +.vercel +.env*.local diff --git a/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx similarity index 81% rename from src/app/(auth)/sign-in/[[...sign-in]]/page.tsx rename to app/(auth)/sign-in/[[...sign-in]]/page.tsx index 4b0dd55..77cc40c 100644 --- a/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx @@ -2,8 +2,6 @@ import { SignIn } from "@clerk/nextjs"; -export const dynamic = "force-static"; - const SignInPage = () => ( ); diff --git a/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx similarity index 81% rename from src/app/(auth)/sign-up/[[...sign-up]]/page.tsx rename to app/(auth)/sign-up/[[...sign-up]]/page.tsx index d94ba4e..a358619 100644 --- a/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx +++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx @@ -2,8 +2,6 @@ import { SignUp } from "@clerk/nextjs"; -export const dynamic = "force-static"; - const SignUpPage = () => ( ); diff --git a/src/app/_components/RoomList.tsx b/app/(client)/dashboard/RoomList.tsx similarity index 78% rename from src/app/_components/RoomList.tsx rename to app/(client)/dashboard/RoomList.tsx index bc992f9..50f6517 100644 --- a/src/app/_components/RoomList.tsx +++ b/app/(client)/dashboard/RoomList.tsx @@ -1,56 +1,32 @@ "use client"; import Link from "next/link"; -import { configureAbly, useChannel } from "@ably-labs/react-hooks"; import { useEffect, useState } from "react"; import { IoEnterOutline, IoTrashBinOutline } from "react-icons/io5"; -import { env } from "@/env.mjs"; -import LoadingIndicator from "./LoadingIndicator"; +import { env } from "env.mjs"; +import LoadingIndicator from "@/_components/LoadingIndicator"; import { useUser } from "@clerk/nextjs"; -import { createRoom, deleteRoom, getRooms } from "@/server/actions/room"; - -export const dynamic = "force-dynamic"; -export const revalidate = 0; -export const fetchCache = "force-no-store"; +import { useChannel } from "ably/react"; +import type { RoomsResponse } from "@/_utils/types"; const RoomList = () => { const { user } = useUser(); - configureAbly({ - key: env.NEXT_PUBLIC_ABLY_PUBLIC_KEY, - clientId: user?.id, - recover: (_, cb) => { - cb(true); - }, - }); - useChannel( `${env.NEXT_PUBLIC_APP_ENV}-${user?.id}`, () => void getRoomsHandler() ); const [roomName, setRoomName] = useState(""); - const [roomsFromDb, setRoomsFromDb] = useState< - | { - id: string; - createdAt: Date; - roomName: string; - }[] - | { - id: string; - created_at: Date | null; - userId: string; - roomName: string | null; - storyName: string | null; - visible: boolean; - scale: string; - }[] - | undefined - | null - >(undefined); + const [roomsFromDb, setRoomsFromDb] = useState(undefined); const createRoomHandler = async () => { - await createRoom(roomName); + await fetch("/api/internal/room", { + cache: "no-cache", + method: "POST", + body: JSON.stringify({ name: roomName }), + }); + setRoomName(""); (document.querySelector("#roomNameInput") as HTMLInputElement).value = ""; (document.querySelector("#new-room-modal") as HTMLInputElement).checked = @@ -58,18 +34,21 @@ const RoomList = () => { }; const getRoomsHandler = async () => { - const dbRooms = await getRooms(); + const dbRoomsResponse = await fetch("/api/internal/room", { + cache: "no-cache", + method: "GET", + }); + const dbRooms = (await dbRoomsResponse.json()) as RoomsResponse; setRoomsFromDb(dbRooms); }; const deleteRoomHandler = async (roomId: string) => { - await deleteRoom(roomId); + await fetch(`/api/internal/room/${roomId}`, { + cache: "no-cache", + method: "DELETE", + }); }; - useEffect(() => { - void getRoomsHandler(); - }, []); - return (
{/* Modal for Adding Rooms */} diff --git a/src/app/room/[id]/loading.tsx b/app/(client)/dashboard/loading.tsx similarity index 51% rename from src/app/room/[id]/loading.tsx rename to app/(client)/dashboard/loading.tsx index 4acbf22..42143df 100644 --- a/src/app/room/[id]/loading.tsx +++ b/app/(client)/dashboard/loading.tsx @@ -1,4 +1,4 @@ -import LoadingIndicator from "@/app/_components/LoadingIndicator"; +import LoadingIndicator from "@/_components/LoadingIndicator"; export default function Loading() { return ; diff --git a/src/app/dashboard/page.tsx b/app/(client)/dashboard/page.tsx similarity index 76% rename from src/app/dashboard/page.tsx rename to app/(client)/dashboard/page.tsx index 687dd36..80c1599 100644 --- a/src/app/dashboard/page.tsx +++ b/app/(client)/dashboard/page.tsx @@ -1,15 +1,18 @@ -import RoomList from "@/app/_components/RoomList"; +import RoomList from "@/(client)/dashboard/RoomList"; import { FaShieldAlt } from "react-icons/fa"; import { GiStarFormation } from "react-icons/gi"; -import { isAdmin, isVIP } from "@/utils/helpers"; +import { isAdmin, isVIP } from "@/_utils/helpers"; import { currentUser } from "@clerk/nextjs"; +export const runtime = "edge"; +export const preferredRegion = ["pdx1"]; + export default async function Dashboard() { const user = await currentUser(); return (
-

+

Hi, {user?.firstName ?? user?.username}!{" "} {isAdmin(user?.publicMetadata) && ( diff --git a/app/(client)/layout.tsx b/app/(client)/layout.tsx new file mode 100644 index 0000000..6972022 --- /dev/null +++ b/app/(client)/layout.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { AblyProvider } from "ably/react"; +import * as Ably from "ably"; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + const client = new Ably.Realtime.Promise({ + authUrl: "/api/internal/ably", + }); + + return {children}; +} diff --git a/app/(client)/room/[id]/NoRoomUI.tsx b/app/(client)/room/[id]/NoRoomUI.tsx new file mode 100644 index 0000000..adcd2e0 --- /dev/null +++ b/app/(client)/room/[id]/NoRoomUI.tsx @@ -0,0 +1,23 @@ +"use client"; + +import Link from "next/link"; + +const VoteUI = () => { + return ( + +

4️⃣0️⃣4️⃣

+

+ Oops! This room does not appear to exist, or may have been deleted! 😢 +

+ + Back to Home + +
+ ); +}; + +export default VoteUI; diff --git a/src/app/_components/VoteUI.tsx b/app/(client)/room/[id]/VoteUI.tsx similarity index 75% rename from src/app/_components/VoteUI.tsx rename to app/(client)/room/[id]/VoteUI.tsx index 453b969..ade2464 100644 --- a/src/app/_components/VoteUI.tsx +++ b/app/(client)/room/[id]/VoteUI.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import { useEffect, useState } from "react"; -import { EventTypes } from "@/utils/types"; +import { EventTypes } from "@/_utils/types"; import { useParams } from "next/navigation"; import { @@ -16,21 +16,15 @@ import { IoSaveOutline, } from "react-icons/io5"; import { GiStarFormation } from "react-icons/gi"; -import { configureAbly, useChannel, usePresence } from "@ably-labs/react-hooks"; -import Link from "next/link"; import { FaShieldAlt } from "react-icons/fa"; import { RiVipCrownFill } from "react-icons/ri"; -import { env } from "@/env.mjs"; -import { isAdmin, isVIP, jsonToCsv } from "@/utils/helpers"; -import type { PresenceItem } from "@/utils/types"; -import LoadingIndicator from "@/app/_components/LoadingIndicator"; +import { env } from "env.mjs"; +import { isAdmin, isVIP, jsonToCsv } from "app/_utils/helpers"; +import type { PresenceItem, RoomResponse, VoteResponse } from "@/_utils/types"; +import LoadingIndicator from "@/_components/LoadingIndicator"; import { useUser } from "@clerk/nextjs"; -import { getRoom, setRoom } from "@/server/actions/room"; -import { getVotes, setVote } from "@/server/actions/vote"; - -export const dynamic = "force-dynamic"; -export const revalidate = 0; -export const fetchCache = "force-no-store"; +import { useChannel, usePresence } from "ably/react"; +import NoRoomUI from "./NoRoomUI"; const VoteUI = () => { const params = useParams(); @@ -41,65 +35,33 @@ const VoteUI = () => { const [roomScale, setRoomScale] = useState(""); const [copied, setCopied] = useState(false); - const [roomFromDb, setRoomFromDb] = useState< - | { - id: string; - created_at: Date | null; - userId: string; - roomName: string | null; - storyName: string | null; - visible: boolean; - scale: string | null; - logs: { - id: string; - created_at: Date | null; - userId: string; - roomId: string; - roomName: string | null; - storyName: string | null; - scale: string | null; - votes: unknown; - }[]; - } - | undefined - | null - >(); + const [roomFromDb, setRoomFromDb] = useState(); - const [votesFromDb, setVotesFromDb] = useState< - | { - id: string; - created_at: Date | null; - userId: string; - roomId: string; - value: string; - }[] - | undefined - | null - >(undefined); + const [votesFromDb, setVotesFromDb] = useState(undefined); const getRoomHandler = async () => { - const dbRoom = await getRoom(roomId); + const dbRoomResponse = await fetch(`/api/internal/room/${roomId}`, { + cache: "no-cache", + method: "GET", + }); + const dbRoom = (await dbRoomResponse.json()) as RoomResponse; setRoomFromDb(dbRoom); }; const getVotesHandler = async () => { - const dbVotes = await getVotes(roomId); + const dbVotesResponse = await fetch(`/api/internal/room/${roomId}/votes`, { + cache: "no-cache", + method: "GET", + }); + const dbVotes = (await dbVotesResponse.json()) as VoteResponse; setVotesFromDb(dbVotes); }; - configureAbly({ - key: env.NEXT_PUBLIC_ABLY_PUBLIC_KEY, - clientId: user ? user.id : "unknown", - recover: (_, cb) => { - cb(true); - }, - }); - - const [channel] = useChannel( + useChannel( { channelName: `${env.NEXT_PUBLIC_APP_ENV}-${roomId}`, }, - ({ name }) => { + ({ name }: { name: string }) => { if (name === EventTypes.ROOM_UPDATE) { void getVotesHandler(); void getRoomHandler(); @@ -109,7 +71,7 @@ const VoteUI = () => { } ); - const [presenceData] = usePresence( + const { presenceData } = usePresence( `${env.NEXT_PUBLIC_APP_ENV}-${roomId}`, { name: (user?.fullName ?? user?.username) || "", @@ -120,18 +82,7 @@ const VoteUI = () => { } ); - // Subscribe on mount and unsubscribe on unmount - useEffect(() => { - window.addEventListener("beforeunload", () => channel.presence.leave()); - return () => { - window.removeEventListener("beforeunload", () => - channel.presence.leave() - ); - channel.presence.leave(); - }; - }, [channel.presence, roomId]); - - // Init story name + // Init Story name useEffect(() => { if (roomFromDb) { setStoryNameText(roomFromDb.storyName || ""); @@ -155,7 +106,13 @@ const VoteUI = () => { const setVoteHandler = async (value: string) => { if (roomFromDb) { - await setVote(value, roomFromDb.id); + await fetch(`/api/internal/room/${roomId}/vote`, { + cache: "no-cache", + method: "PUT", + body: JSON.stringify({ + value, + }), + }); } }; @@ -165,14 +122,17 @@ const VoteUI = () => { log = false ) => { if (roomFromDb) { - await setRoom( - storyNameText, - visible, - roomScale, - roomFromDb.id, - reset, - log - ); + await fetch(`/api/internal/room/${roomId}`, { + cache: "no-cache", + method: "PUT", + body: JSON.stringify({ + name: storyNameText, + visible, + scale: roomScale, + reset, + log, + }), + }); } }; @@ -238,15 +198,13 @@ const VoteUI = () => { if (!!matchedVote) { return
{matchedVote.value}
; } else { - return ; + return ; } } else if (!!matchedVote) { - return ( - - ); + return ; } else { return ( - + ); } }; @@ -257,9 +215,9 @@ const VoteUI = () => { // Room has been loaded } else if (roomFromDb) { return ( - +
{roomFromDb.roomName}
-
+
ID:
{roomFromDb.id}
@@ -276,13 +234,11 @@ const VoteUI = () => {
{roomFromDb && ( -
+
-

- Story: {roomFromDb.storyName} -

+

Story: {roomFromDb.storyName}

-
    +
      {presenceData && presenceData .filter( @@ -299,7 +255,7 @@ const VoteUI = () => { key={presenceItem.clientId} className="flex flex-row items-center justify-center gap-2" > -
      +
      {`${presenceItem.data.name}'s { />
      -

      +

      {presenceItem.data.name}{" "} {presenceItem.data.isAdmin && ( { })}

    -
    +
    {roomFromDb.scale?.split(",").map((scaleItem, index) => { return ( ); } diff --git a/src/app/_components/LoadingIndicator.tsx b/app/_components/LoadingIndicator.tsx similarity index 100% rename from src/app/_components/LoadingIndicator.tsx rename to app/_components/LoadingIndicator.tsx diff --git a/src/server/ably.ts b/app/_lib/ably.ts similarity index 72% rename from src/server/ably.ts rename to app/_lib/ably.ts index 8a6b4f2..97c73c3 100644 --- a/src/server/ably.ts +++ b/app/_lib/ably.ts @@ -1,5 +1,5 @@ -import { env } from "@/env.mjs"; -import type { EventType } from "../utils/types"; +import { env } from "env.mjs"; +import type { EventType } from "@/_utils/types"; export const publishToChannel = async ( channel: string, @@ -12,7 +12,7 @@ export const publishToChannel = async ( method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Basic ${btoa(env.ABLY_PRIVATE_KEY)}`, + Authorization: `Basic ${btoa(env.ABLY_API_KEY)}`, }, body: JSON.stringify({ name: event, diff --git a/src/server/db.ts b/app/_lib/db.ts similarity index 75% rename from src/server/db.ts rename to app/_lib/db.ts index 6ac6acb..323937c 100644 --- a/src/server/db.ts +++ b/app/_lib/db.ts @@ -1,7 +1,7 @@ import { neon, neonConfig } from "@neondatabase/serverless"; import { drizzle } from "drizzle-orm/neon-http"; -import { env } from "@/env.mjs"; -import * as schema from "@/server/schema"; +import { env } from "env.mjs"; +import * as schema from "app/_lib/schema"; neonConfig.fetchConnectionCache = true; diff --git a/src/server/redis.ts b/app/_lib/redis.ts similarity index 95% rename from src/server/redis.ts rename to app/_lib/redis.ts index bab9d0c..253cc8f 100644 --- a/src/server/redis.ts +++ b/app/_lib/redis.ts @@ -1,5 +1,5 @@ import { Redis } from "@upstash/redis"; -import { env } from "@/env.mjs"; +import { env } from "env.mjs"; export const redis = Redis.fromEnv(); diff --git a/src/server/schema.ts b/app/_lib/schema.ts similarity index 100% rename from src/server/schema.ts rename to app/_lib/schema.ts diff --git a/src/server/unkey.ts b/app/_lib/unkey.ts similarity index 71% rename from src/server/unkey.ts rename to app/_lib/unkey.ts index 969e814..7380df5 100644 --- a/src/server/unkey.ts +++ b/app/_lib/unkey.ts @@ -1,13 +1,8 @@ -// import { Unkey, verifyKey } from "@unkey/api"; import { verifyKey } from "@unkey/api"; import type { NextRequest } from "next/server"; -// import { env } from "@/env.mjs"; - -// const unkey = new Unkey({token: env.UNKEY_ROOT_KEY}) export const validateRequest = async (req: NextRequest) => { const authorization = req.headers.get("authorization"); - // Get the auth bearer token if it exists if (authorization) { const key = authorization.split("Bearer ").at(1); if (key) { diff --git a/src/utils/helpers.ts b/app/_utils/helpers.ts similarity index 100% rename from src/utils/helpers.ts rename to app/_utils/helpers.ts diff --git a/app/_utils/types.ts b/app/_utils/types.ts new file mode 100644 index 0000000..e458662 --- /dev/null +++ b/app/_utils/types.ts @@ -0,0 +1,69 @@ +type BetterEnum = T[keyof T]; + +export const EventTypes = { + ROOM_LIST_UPDATE: "room.list.update", + ROOM_UPDATE: "room.update", + VOTE_UPDATE: "vote.update", + STATS_UPDATE: "stats.update", +} as const; +export type EventType = BetterEnum; + +export interface PresenceItem { + name: string; + image: string; + client_id: string; + isAdmin: boolean; + isVIP: boolean; +} + +export type RoomsResponse = + | { + id: string; + createdAt: Date; + roomName: string; + }[] + | { + roomName: string | null; + id: string; + created_at: Date | null; + userId: string; + storyName: string | null; + visible: boolean; + scale: string; + }[] + | null + | undefined; + +export type RoomResponse = + | { + id: string; + created_at: Date | null; + userId: string; + roomName: string | null; + storyName: string | null; + visible: boolean; + scale: string | null; + logs: { + id: string; + created_at: Date | null; + userId: string; + roomId: string; + roomName: string | null; + storyName: string | null; + scale: string | null; + votes: unknown; + }[]; + } + | undefined + | null; + +export type VoteResponse = + | { + id: string; + value: string; + created_at: Date | null; + userId: string; + roomId: string; + }[] + | null + | undefined; diff --git a/src/utils/webhookHelpers.ts b/app/_utils/webhookHelpers.ts similarity index 88% rename from src/utils/webhookHelpers.ts rename to app/_utils/webhookHelpers.ts index c760916..8f3bb8c 100644 --- a/src/utils/webhookHelpers.ts +++ b/app/_utils/webhookHelpers.ts @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; -import { db } from "../server/db"; -import { rooms } from "../server/schema"; -import { env } from "@/env.mjs"; +import { db } from "../_lib/db"; +import { rooms } from "../_lib/schema"; +import { env } from "env.mjs"; export const onUserDeletedHandler = async (userId: string | undefined) => { if (!userId) { diff --git a/app/api/README.md b/app/api/README.md new file mode 100644 index 0000000..9a4adaf --- /dev/null +++ b/app/api/README.md @@ -0,0 +1,9 @@ +# API + +## Categories: + +1. Internal - Only to be used within the application by signed in users. +2. External - Only to be used outside of the application. + - Public - Can be used by anyone. + - Private - Can only be used by a user who uses a valid API Key from Unkey +3. Webhooks - Only to be used by external services to send data to the application. Sub-routes are for different handlers. diff --git a/src/app/api/private/ping/route.ts b/app/api/external/private/ping/route.ts similarity index 100% rename from src/app/api/private/ping/route.ts rename to app/api/external/private/ping/route.ts diff --git a/src/app/api/public/ping/route.ts b/app/api/external/public/ping/route.ts similarity index 100% rename from src/app/api/public/ping/route.ts rename to app/api/external/public/ping/route.ts diff --git a/app/api/internal/ably/route.ts b/app/api/internal/ably/route.ts new file mode 100644 index 0000000..321554c --- /dev/null +++ b/app/api/internal/ably/route.ts @@ -0,0 +1,37 @@ +import { NextResponse } from "next/server"; + +import * as Ably from "ably/promises"; +import { env } from "env.mjs"; +import { currentUser } from "@clerk/nextjs"; + +async function handler() { + const user = await currentUser(); + + if (!env.ABLY_API_KEY) { + return new Response( + `Missing ABLY_API_KEY environment variable. + If you're running locally, please ensure you have a ./.env file with a value for ABLY_API_KEY=your-key. + If you're running in Netlify, make sure you've configured env variable ABLY_API_KEY. + Please see README.md for more details on configuring your Ably API Key.`, + { + status: 500, + statusText: `Missing ABLY_API_KEY environment variable. + If you're running locally, please ensure you have a ./.env file with a value for ABLY_API_KEY=your-key. + If you're running in Netlify, make sure you've configured env variable ABLY_API_KEY. + Please see README.md for more details on configuring your Ably API Key.`, + } + ); + } + + const client = new Ably.Rest(env.ABLY_API_KEY); + const tokenRequestData = await client.auth.createTokenRequest({ + clientId: user?.id, + }); + + return NextResponse.json(tokenRequestData, { + status: 200, + statusText: "SUCCESS", + }); +} + +export { handler as POST, handler as GET }; diff --git a/app/api/internal/room/[roomId]/route.ts b/app/api/internal/room/[roomId]/route.ts new file mode 100644 index 0000000..2bb360a --- /dev/null +++ b/app/api/internal/room/[roomId]/route.ts @@ -0,0 +1,184 @@ +import { type NextRequest, NextResponse } from "next/server"; + +import { db } from "@/_lib/db"; +import { logs, rooms, votes } from "@/_lib/schema"; +import { eq } from "drizzle-orm"; +import { publishToChannel } from "@/_lib/ably"; +import { EventTypes } from "@/_utils/types"; +import { invalidateCache } from "@/_lib/redis"; +import { createId } from "@paralleldrive/cuid2"; +import { getAuth } from "@clerk/nextjs/server"; + +export const runtime = "edge"; +export const preferredRegion = ["pdx1"]; + +export async function GET( + request: Request, + { params }: { params: { roomId: string } } +) { + if (!params.roomId) { + return new NextResponse("RoomId Missing!", { + status: 400, + statusText: "BAD REQUEST!", + }); + } + + const roomFromDb = await db.query.rooms.findFirst({ + where: eq(rooms.id, params.roomId), + with: { + logs: true, + }, + }); + + return NextResponse.json(roomFromDb, { + status: 200, + statusText: "SUCCESS", + }); +} + +export async function DELETE( + request: Request, + { params }: { params: { roomId: string } } +) { + const { userId } = getAuth(request as NextRequest); + + if (!params.roomId) { + return new NextResponse("RoomId Missing!", { + status: 400, + statusText: "BAD REQUEST!", + }); + } + + const deletedRoom = await db + .delete(rooms) + .where(eq(rooms.id, params.roomId)) + .returning(); + + const success = deletedRoom.length > 0; + + if (success) { + await publishToChannel( + `${userId}`, + EventTypes.ROOM_LIST_UPDATE, + JSON.stringify(deletedRoom) + ); + + await publishToChannel( + `${params.roomId}`, + EventTypes.ROOM_UPDATE, + JSON.stringify(deletedRoom) + ); + + await invalidateCache(`kv_roomlist_${userId}`); + + await publishToChannel( + `${userId}`, + EventTypes.ROOM_LIST_UPDATE, + JSON.stringify(deletedRoom) + ); + + return NextResponse.json(deletedRoom, { + status: 200, + statusText: "SUCCESS", + }); + } + + return NextResponse.json( + { error: "Error deleting room!" }, + { + status: 500, + statusText: "ERROR", + } + ); +} + +export async function PUT( + request: Request, + { params }: { params: { roomId: string } } +) { + const { userId } = getAuth(request as NextRequest); + + const reqBody = (await request.json()) as { + name: string; + visible: boolean; + scale: string; + reset: boolean; + log: boolean; + }; + + if (!params.roomId) { + return new NextResponse("RoomId Missing!", { + status: 400, + statusText: "BAD REQUEST!", + }); + } + + if (reqBody.reset) { + if (reqBody.log) { + const oldRoom = await db.query.rooms.findFirst({ + where: eq(rooms.id, params.roomId), + with: { + votes: true, + logs: true, + }, + }); + + oldRoom && + (await db.insert(logs).values({ + id: `log_${createId()}`, + userId: userId || "", + roomId: params.roomId, + scale: oldRoom.scale, + votes: oldRoom.votes.map((vote) => { + return { + name: vote.userId, + value: vote.value, + }; + }), + roomName: oldRoom.roomName, + storyName: oldRoom.storyName, + })); + } + + await db.delete(votes).where(eq(votes.roomId, params.roomId)); + + await invalidateCache(`kv_votes_${params.roomId}`); + } + + const newRoom = await db + .update(rooms) + .set({ + storyName: reqBody.name, + visible: reqBody.visible, + scale: [...new Set(reqBody.scale.split(","))] + .filter((item) => item !== "") + .toString(), + }) + .where(eq(rooms.id, params.roomId)) + .returning(); + + const success = newRoom.length > 0; + + if (success) { + await publishToChannel( + `${params.roomId}`, + EventTypes.ROOM_UPDATE, + JSON.stringify(newRoom) + ); + } + + if (success) { + return NextResponse.json(newRoom, { + status: 200, + statusText: "SUCCESS", + }); + } + + return NextResponse.json( + { error: "Room update failed" }, + { + status: 500, + statusText: "ERROR", + } + ); +} diff --git a/app/api/internal/room/[roomId]/vote/route.ts b/app/api/internal/room/[roomId]/vote/route.ts new file mode 100644 index 0000000..504b9e6 --- /dev/null +++ b/app/api/internal/room/[roomId]/vote/route.ts @@ -0,0 +1,76 @@ +import { type NextRequest, NextResponse } from "next/server"; + +import { invalidateCache } from "@/_lib/redis"; +import { db } from "@/_lib/db"; +import { votes } from "@/_lib/schema"; +import { createId } from "@paralleldrive/cuid2"; +import { publishToChannel } from "@/_lib/ably"; +import { EventTypes } from "@/_utils/types"; +import { getAuth } from "@clerk/nextjs/server"; + +export const runtime = "edge"; +export const preferredRegion = ["pdx1"]; + +export async function PUT( + request: Request, + { params }: { params: { roomId: string } } +) { + const { userId } = getAuth(request as NextRequest); + + if (!params.roomId) { + return new NextResponse("RoomId Missing!", { + status: 400, + statusText: "BAD REQUEST!", + }); + } + + const reqBody = (await request.json()) as { value: string }; + + const upsertResult = await db + .insert(votes) + .values({ + id: `vote_${createId()}`, + value: reqBody.value, + userId: userId || "", + roomId: params.roomId, + }) + .onConflictDoUpdate({ + target: [votes.userId, votes.roomId], + set: { + value: reqBody.value, + userId: userId || "", + roomId: params.roomId, + }, + }); + + const success = upsertResult.rowCount > 0; + + if (success) { + await invalidateCache(`kv_votes_${params.roomId}`); + + await publishToChannel( + `${params.roomId}`, + EventTypes.VOTE_UPDATE, + reqBody.value + ); + + await publishToChannel( + `stats`, + EventTypes.STATS_UPDATE, + JSON.stringify(success) + ); + + return NextResponse.json(upsertResult, { + status: 200, + statusText: "SUCCESS", + }); + } + + return NextResponse.json( + { error: "Failed to set vote!" }, + { + status: 500, + statusText: "ERROR", + } + ); +} diff --git a/app/api/internal/room/[roomId]/votes/route.ts b/app/api/internal/room/[roomId]/votes/route.ts new file mode 100644 index 0000000..fe2315c --- /dev/null +++ b/app/api/internal/room/[roomId]/votes/route.ts @@ -0,0 +1,49 @@ +import { NextResponse } from "next/server"; + +import { fetchCache, setCache } from "@/_lib/redis"; +import { db } from "@/_lib/db"; +import { votes } from "@/_lib/schema"; +import { eq } from "drizzle-orm"; + +export const runtime = "edge"; +export const preferredRegion = ["pdx1"]; + +export async function GET( + request: Request, + { params }: { params: { roomId: string } } +) { + if (!params.roomId) { + return new NextResponse("RoomId Missing!", { + status: 400, + statusText: "BAD REQUEST!", + }); + } + + const cachedResult = await fetchCache< + { + id: string; + value: string; + created_at: Date; + userId: string; + roomId: string; + }[] + >(`kv_votes_${params.roomId}`); + + if (cachedResult) { + return NextResponse.json(cachedResult, { + status: 200, + statusText: "SUCCESS!", + }); + } else { + const votesByRoomId = await db.query.votes.findMany({ + where: eq(votes.roomId, params.roomId), + }); + + await setCache(`kv_votes_${params.roomId}`, votesByRoomId); + + return NextResponse.json(votesByRoomId, { + status: 200, + statusText: "SUCCESS!", + }); + } +} diff --git a/app/api/internal/room/route.ts b/app/api/internal/room/route.ts new file mode 100644 index 0000000..8d3af22 --- /dev/null +++ b/app/api/internal/room/route.ts @@ -0,0 +1,88 @@ +import { type NextRequest, NextResponse } from "next/server"; + +import { fetchCache, invalidateCache, setCache } from "@/_lib/redis"; +import { db } from "@/_lib/db"; +import { rooms } from "@/_lib/schema"; +import { eq } from "drizzle-orm"; +import { createId } from "@paralleldrive/cuid2"; +import { publishToChannel } from "@/_lib/ably"; +import { EventTypes } from "@/_utils/types"; +import { getAuth } from "@clerk/nextjs/server"; + +export const runtime = "edge"; +export const preferredRegion = ["pdx1"]; + +export async function GET(request: Request) { + const { userId } = getAuth(request as NextRequest); + + const cachedResult = await fetchCache< + { + id: string; + createdAt: Date; + roomName: string; + }[] + >(`kv_roomlist_${userId}`); + + if (cachedResult) { + return NextResponse.json(cachedResult, { + status: 200, + statusText: "SUCCESS", + }); + } else { + const roomList = await db.query.rooms.findMany({ + where: eq(rooms.userId, userId || ""), + }); + + await setCache(`kv_roomlist_${userId}`, roomList); + + return NextResponse.json(roomList, { + status: 200, + statusText: "SUCCESS", + }); + } +} + +export async function POST(request: Request) { + const { userId } = getAuth(request as NextRequest); + + const reqBody = (await request.json()) as { name: string }; + + const room = await db + .insert(rooms) + .values({ + id: `room_${createId()}`, + userId: userId || "", + roomName: reqBody.name, + storyName: "First Story!", + scale: "0.5,1,2,3,5,8", + visible: false, + }) + .returning(); + + const success = room.length > 0; + + if (room) { + await invalidateCache(`kv_roomlist_${userId}`); + + await publishToChannel( + `${userId}`, + EventTypes.ROOM_LIST_UPDATE, + JSON.stringify(room) + ); + } + + if (success) { + return NextResponse.json(room, { + status: 200, + statusText: "SUCCESS", + }); + } + + return NextResponse.json( + { error: "Failed to create room!" }, + { + status: 500, + statusText: "ERROR", + } + ); +} diff --git a/src/app/api/webhooks/route.ts b/app/api/webhooks/clerk/route.ts similarity index 97% rename from src/app/api/webhooks/route.ts rename to app/api/webhooks/clerk/route.ts index 38483a6..0cdb5fa 100644 --- a/src/app/api/webhooks/route.ts +++ b/app/api/webhooks/clerk/route.ts @@ -2,12 +2,12 @@ import { type NextRequest, NextResponse } from "next/server"; import { onUserCreatedHandler, onUserDeletedHandler, -} from "@/utils/webhookHelpers"; +} from "app/_utils/webhookHelpers"; import { headers } from "next/headers"; import type { WebhookEvent } from "@clerk/nextjs/server"; import { Webhook } from "svix"; -import { env } from "@/env.mjs"; +import { env } from "env.mjs"; export const runtime = "edge"; export const preferredRegion = ["pdx1"]; diff --git a/src/app/apple-icon.png b/app/apple-icon.png similarity index 100% rename from src/app/apple-icon.png rename to app/apple-icon.png diff --git a/src/app/favicon.ico b/app/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to app/favicon.ico diff --git a/src/styles/globals.css b/app/globals.css similarity index 100% rename from src/styles/globals.css rename to app/globals.css diff --git a/src/app/layout.tsx b/app/layout.tsx similarity index 76% rename from src/app/layout.tsx rename to app/layout.tsx index 2e7b594..53b5b3c 100644 --- a/src/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,7 @@ import { ClerkLoaded, ClerkProvider } from "@clerk/nextjs"; -import Footer from "@/app/_components/Footer"; -import Header from "@/app/_components/Header"; -import "@/styles/globals.css"; +import Footer from "@/_components/Footer"; +import Header from "@/_components/Header"; +import "@/globals.css"; import { dark } from "@clerk/themes"; export const metadata = { @@ -20,7 +20,11 @@ export default function RootLayout({ baseTheme: dark, }} > - +
    diff --git a/app/loading.tsx b/app/loading.tsx new file mode 100644 index 0000000..42143df --- /dev/null +++ b/app/loading.tsx @@ -0,0 +1,5 @@ +import LoadingIndicator from "@/_components/LoadingIndicator"; + +export default function Loading() { + return ; +} diff --git a/src/app/page.tsx b/app/page.tsx similarity index 100% rename from src/app/page.tsx rename to app/page.tsx diff --git a/drizzle.config.ts b/drizzle.config.ts index bed95ac..4c22214 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -2,7 +2,7 @@ import type { Config } from "drizzle-kit"; import "dotenv/config"; export default { - schema: "./src/server/schema.ts", + schema: "./app/_lib/schema.ts", out: "./drizzle/generated", driver: "pg", breakpoints: true, diff --git a/src/env.mjs b/env.mjs similarity index 87% rename from src/env.mjs rename to env.mjs index 6c2da00..2f62a33 100644 --- a/src/env.mjs +++ b/env.mjs @@ -9,21 +9,19 @@ export const env = createEnv({ UPSTASH_REDIS_EXPIRY_SECONDS: z.string(), UPSTASH_RATELIMIT_REQUESTS: z.string(), UPSTASH_RATELIMIT_SECONDS: z.string(), - ABLY_PRIVATE_KEY: z.string(), + ABLY_API_KEY: z.string(), APP_ENV: z.string(), UNKEY_ROOT_KEY: z.string(), CLERK_SECRET_KEY: z.string(), CLERK_WEBHOOK_SIGNING_SECRET: z.string(), }, client: { - NEXT_PUBLIC_ABLY_PUBLIC_KEY: z.string(), NEXT_PUBLIC_APP_ENV: z.string(), NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string(), NEXT_PUBLIC_CLERK_SIGN_UP_URL: z.string(), NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string(), }, experimental__runtimeEnv: { - NEXT_PUBLIC_ABLY_PUBLIC_KEY: process.env.NEXT_PUBLIC_ABLY_PUBLIC_KEY, NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, diff --git a/src/middleware.ts b/middleware.ts similarity index 64% rename from src/middleware.ts rename to middleware.ts index db4498e..d1a3bd7 100644 --- a/src/middleware.ts +++ b/middleware.ts @@ -1,5 +1,5 @@ import { authMiddleware, redirectToSignIn } from "@clerk/nextjs"; -import { validateRequest } from "./server/unkey"; +import { validateRequest } from "./app/_lib/unkey"; import { NextResponse } from "next/server"; import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; @@ -15,7 +15,12 @@ const rateLimit = new Ratelimit({ }); export default authMiddleware({ - publicRoutes: ["/", "/api/public/(.*)", "/api/webhooks"], + publicRoutes: [ + "/", + "/api/external/public/(.*)", + "/api/webhooks", + "/api/webhooks/(.*)", + ], afterAuth: async (auth, req) => { if (!auth.userId && auth.isPublicRoute) { const { success } = await rateLimit.limit(req.ip || ""); @@ -28,18 +33,19 @@ export default authMiddleware({ }); } - if (req.nextUrl.pathname.includes("/api/private")) { + if (req.nextUrl.pathname.includes("/api/internal")) { const { success } = await rateLimit.limit(req.ip || ""); - const isValid = await validateRequest(req); - if (isValid && success) { - return NextResponse.next(); - } else if (!success) { + if (!success) { return new NextResponse("TOO MANY REQUESTS", { status: 429, statusText: "Too many requests!", }); - } else if (!isValid) { + } + + if (auth.userId) { + return NextResponse.next(); + } else { return new NextResponse("UNAUTHORIZED", { status: 403, statusText: "Unauthorized!", @@ -47,15 +53,32 @@ export default authMiddleware({ } } - if (auth.userId && !auth.isPublicRoute) { - const requestHeaders = new Headers(req.headers); - requestHeaders.set("Cache-Control", "no-cache"); - return NextResponse.next({ - headers: requestHeaders, - }); + if (req.nextUrl.pathname.includes("/api/external/private")) { + const { success } = await rateLimit.limit(req.ip || ""); + + if (!success) { + return new NextResponse("TOO MANY REQUESTS", { + status: 429, + statusText: "Too many requests!", + }); + } + + const isValid = await validateRequest(req); + + if (isValid) { + return NextResponse.next(); + } else { + return new NextResponse("UNAUTHORIZED", { + status: 403, + statusText: "Unauthorized!", + }); + } } if (!auth.userId && !auth.isPublicRoute) { + if (req.nextUrl.pathname.includes("/api")) { + return NextResponse.next(); + } // This is annoying... // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any return redirectToSignIn({ returnBackUrl: req.url }); diff --git a/next.config.mjs b/next.config.mjs index 9e11282..f77e319 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,4 @@ -import "./src/env.mjs"; +import "./env.mjs"; /** @type {import("next").NextConfig} */ const config = { @@ -15,11 +15,6 @@ const config = { "img.clerk.com", ], }, - experimental: { - serverActions: true, - serverMinification: true, - swcMinify: true, - }, }; export default config; diff --git a/package.json b/package.json index 90d9a19..54d6d31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sprintpadawan", - "version": "2.3.0", + "version": "3.0.0", "description": "Plan. Sprint. Repeat.", "private": true, "scripts": { @@ -10,50 +10,47 @@ "lint": "next lint", "start": "next start", "db:push": "pnpm drizzle-kit push:pg", - "db:studio": "pnpm drizzle-kit studio" + "db:studio": "pnpm drizzle-kit studio", + "init:env": "cp .env.example .env" }, "dependencies": { - "@ably-labs/react-hooks": "^2.1.1", - "@clerk/nextjs": "^4.23.5", - "@clerk/themes": "^1.7.5", + "@clerk/nextjs": "^4.24.1", + "@clerk/themes": "^1.7.6", "@neondatabase/serverless": "^0.6.0", "@paralleldrive/cuid2": "^2.2.2", "@t3-oss/env-nextjs": "^0.6.1", "@unkey/api": "^0.8.0", "@upstash/ratelimit": "^0.4.4", "@upstash/redis": "^1.22.0", - "autoprefixer": "^10.4.15", + "ably": "^1.2.44", + "autoprefixer": "^10.4.16", "csv42": "^5.0.0", "dotenv": "^16.3.1", "drizzle-orm": "^0.28.6", - "next": "^13.5.1", + "next": "^13.5.2", "nextjs-cors": "^2.1.2", "postcss": "^8.4.30", "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.11.0", "sharp": "^0.32.6", - "superjson": "1.13.1", "svix": "^1.12.0", "zod": "^3.22.2" }, "devDependencies": { - "@types/eslint": "^8.44.2", - "@types/json2csv": "^5.0.4", - "@types/node": "^20.6.3", + "@types/eslint": "^8.44.3", + "@types/node": "^20.6.4", "@types/react": "^18.2.22", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "bufferutil": "^4.0.7", - "daisyui": "^3.7.6", + "daisyui": "^3.7.7", "drizzle-kit": "^0.19.13", - "encoding": "^0.1.13", - "eslint": "^8.49.0", - "eslint-config-next": "^13.5.1", + "eslint": "^8.50.0", + "eslint-config-next": "^13.5.2", "pg": "^8.11.3", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", - "utf-8-validate": "6.0.3", - "ws": "^8.14.2" + "utf-8-validate": "6.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df90844..e0485b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,15 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: - '@ably-labs/react-hooks': - specifier: ^2.1.1 - version: 2.1.1(bufferutil@4.0.7)(react-dom@18.2.0)(react@18.2.0)(utf-8-validate@6.0.3) '@clerk/nextjs': - specifier: ^4.23.5 - version: 4.23.5(next@13.5.1)(react-dom@18.2.0)(react@18.2.0) + specifier: ^4.24.1 + version: 4.24.1(next@13.5.2)(react-dom@18.2.0)(react@18.2.0) '@clerk/themes': - specifier: ^1.7.5 - version: 1.7.5(react@18.2.0) + specifier: ^1.7.6 + version: 1.7.6(react@18.2.0) '@neondatabase/serverless': specifier: ^0.6.0 version: 0.6.0 @@ -28,13 +25,16 @@ dependencies: version: 0.8.0 '@upstash/ratelimit': specifier: ^0.4.4 - version: 0.4.4(encoding@0.1.13) + version: 0.4.4 '@upstash/redis': specifier: ^1.22.0 - version: 1.22.0(encoding@0.1.13) + version: 1.22.0 + ably: + specifier: ^1.2.44 + version: 1.2.44(bufferutil@4.0.7)(react-dom@18.2.0)(react@18.2.0)(utf-8-validate@6.0.3) autoprefixer: - specifier: ^10.4.15 - version: 10.4.15(postcss@8.4.30) + specifier: ^10.4.16 + version: 10.4.16(postcss@8.4.30) csv42: specifier: ^5.0.0 version: 5.0.0 @@ -45,11 +45,11 @@ dependencies: specifier: ^0.28.6 version: 0.28.6(@neondatabase/serverless@0.6.0)(pg@8.11.3) next: - specifier: ^13.5.1 - version: 13.5.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.5.2 + version: 13.5.2(react-dom@18.2.0)(react@18.2.0) nextjs-cors: specifier: ^2.1.2 - version: 2.1.2(next@13.5.1) + version: 2.1.2(next@13.5.2) postcss: specifier: ^8.4.30 version: 8.4.30 @@ -65,53 +65,44 @@ dependencies: sharp: specifier: ^0.32.6 version: 0.32.6 - superjson: - specifier: 1.13.1 - version: 1.13.1 svix: specifier: ^1.12.0 - version: 1.12.0(encoding@0.1.13) + version: 1.12.0 zod: specifier: ^3.22.2 version: 3.22.2 devDependencies: '@types/eslint': - specifier: ^8.44.2 - version: 8.44.2 - '@types/json2csv': - specifier: ^5.0.4 - version: 5.0.4 + specifier: ^8.44.3 + version: 8.44.3 '@types/node': - specifier: ^20.6.3 - version: 20.6.3 + specifier: ^20.6.4 + version: 20.6.4 '@types/react': specifier: ^18.2.22 version: 18.2.22 '@typescript-eslint/eslint-plugin': specifier: ^6.7.2 - version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.49.0)(typescript@5.2.2) + version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^6.7.2 - version: 6.7.2(eslint@8.49.0)(typescript@5.2.2) + version: 6.7.2(eslint@8.50.0)(typescript@5.2.2) bufferutil: specifier: ^4.0.7 version: 4.0.7 daisyui: - specifier: ^3.7.6 - version: 3.7.6 + specifier: ^3.7.7 + version: 3.7.7 drizzle-kit: specifier: ^0.19.13 version: 0.19.13 - encoding: - specifier: ^0.1.13 - version: 0.1.13 eslint: - specifier: ^8.49.0 - version: 8.49.0 + specifier: ^8.50.0 + version: 8.50.0 eslint-config-next: - specifier: ^13.5.1 - version: 13.5.1(eslint@8.49.0)(typescript@5.2.2) + specifier: ^13.5.2 + version: 13.5.2(eslint@8.50.0)(typescript@5.2.2) pg: specifier: ^8.11.3 version: 8.11.3 @@ -124,9 +115,6 @@ devDependencies: utf-8-validate: specifier: 6.0.3 version: 6.0.3 - ws: - specifier: ^8.14.2 - version: 8.14.2(bufferutil@4.0.7)(utf-8-validate@6.0.3) packages: @@ -135,20 +123,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@ably-labs/react-hooks@2.1.1(bufferutil@4.0.7)(react-dom@18.2.0)(react@18.2.0)(utf-8-validate@6.0.3): - resolution: {integrity: sha512-Bunqu9GDFInZLpFMfWhUboU1g4W5UXzDfeAqI9ueNIF3p9KIMS7LfgjKBfXsC0DtAWkBgCjL22PvNToiHP92Ig==} - peerDependencies: - react: '>=18.1.0' - react-dom: '>=18.1.0' - dependencies: - ably: 1.2.44(bufferutil@4.0.7)(react-dom@18.2.0)(react@18.2.0)(utf-8-validate@6.0.3) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /@ably/msgpack-js@0.4.0: resolution: {integrity: sha512-IPt/BoiQwCWubqoNik1aw/6M/DleMdrxJOUpSja6xmMRbT2p1TA8oqKWgfZabqzrq8emRNeSl/+4XABPNnW5pQ==} dependencies: @@ -167,11 +141,11 @@ packages: regenerator-runtime: 0.14.0 dev: true - /@clerk/backend@0.29.0: - resolution: {integrity: sha512-2miuqyzmlzboTcLQOtafGw4+InnkFALMWjM9l9gu75KBWg1pUdFYLEDcJV+MHt0ZtY+pZO7aGnPE0Mf7QnG+bA==} + /@clerk/backend@0.29.2: + resolution: {integrity: sha512-7BJiQf7tRKuAq61o1LHMEeBkM8HW8HbjxE2bdSF/V2HdW6XlB+FNXFBFWscf5TCB8p5yJWpzTPmGuWT8GSMD+Q==} engines: {node: '>=14'} dependencies: - '@clerk/types': 3.51.0 + '@clerk/types': 3.52.0 '@peculiar/webcrypto': 1.4.1 '@types/node': 16.18.6 cookie: 0.5.0 @@ -181,24 +155,24 @@ packages: tslib: 2.4.1 dev: false - /@clerk/clerk-react@4.24.2(react@18.2.0): - resolution: {integrity: sha512-nMxhtByDMJ4kXoUu2j75tS9NyEI5S0KI3Zj67WhGxVMAUGbOYPI6gmTv6exORb5kVALF0QmS4FGyOGQ2WZJd3A==} + /@clerk/clerk-react@4.25.1(react@18.2.0): + resolution: {integrity: sha512-+RtK6RNWVlT6jFeOlomi6PM2pDOETyi13zSKL/IgrJ68LV8qrtQikrP0n5v0i0YRDvpjB5lpBF0VgmpAJEGNxQ==} engines: {node: '>=14'} peerDependencies: react: '>=16' dependencies: - '@clerk/shared': 0.22.0(react@18.2.0) - '@clerk/types': 3.51.0 + '@clerk/shared': 0.23.0(react@18.2.0) + '@clerk/types': 3.52.0 react: 18.2.0 tslib: 2.4.1 dev: false - /@clerk/clerk-sdk-node@4.12.5: - resolution: {integrity: sha512-GW0eXhG2P7GCFKnc2j7zOAR2qK0fOQwoZAFvfovXJNyZhlcbg7NKtjsbW6Q8X7qrtaD2UX6SuOvuxQZP633H7w==} + /@clerk/clerk-sdk-node@4.12.7: + resolution: {integrity: sha512-E8VbfLQ6F6VBRrLbB2o4n2/pKma0qQDw1I7rF4wr5/XazwpmLX29/rUJ9XoF0MspKcPeA7dVzQlvcskpsBjuGw==} engines: {node: '>=14'} dependencies: - '@clerk/backend': 0.29.0 - '@clerk/types': 3.51.0 + '@clerk/backend': 0.29.2 + '@clerk/types': 3.52.0 '@types/cookies': 0.7.7 '@types/express': 4.17.14 '@types/node-fetch': 2.6.2 @@ -207,27 +181,27 @@ packages: tslib: 2.4.1 dev: false - /@clerk/nextjs@4.23.5(next@13.5.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-SG8ooFEC5P6lLa1G+239zUu4ciLwmoPei8CnhPzKQ9u90J9ly/j9wVVBm1AX3rY4G+1xMVyR42PUC7wjfvxhoA==} + /@clerk/nextjs@4.24.1(next@13.5.2)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qCP0LVZk4wPoaL5GSAWjsuuukEKtC+elaqCGjzfoCXMUj2MX4P6PIVj7qaN1Er9kmQtpcSaIly4lUw9CK8UrBQ==} engines: {node: '>=14'} peerDependencies: next: '>=10' react: ^17.0.2 || ^18.0.0-0 react-dom: ^17.0.2 || ^18.0.0-0 dependencies: - '@clerk/backend': 0.29.0 - '@clerk/clerk-react': 4.24.2(react@18.2.0) - '@clerk/clerk-sdk-node': 4.12.5 - '@clerk/types': 3.51.0 - next: 13.5.1(react-dom@18.2.0)(react@18.2.0) + '@clerk/backend': 0.29.2 + '@clerk/clerk-react': 4.25.1(react@18.2.0) + '@clerk/clerk-sdk-node': 4.12.7 + '@clerk/types': 3.52.0 + next: 13.5.2(react-dom@18.2.0)(react@18.2.0) path-to-regexp: 6.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) tslib: 2.4.1 dev: false - /@clerk/shared@0.22.0(react@18.2.0): - resolution: {integrity: sha512-AHPypu9gZ3v44PRqiMA56c+YNLc2IzLaPUyiYFYU+xeH/R+wqzGp7OxZoZr/kmzgA8taiVl/bjixWgpuZwzI3A==} + /@clerk/shared@0.23.0(react@18.2.0): + resolution: {integrity: sha512-/1QPDnXexJCdND00hjgq7ATz35SY+oUVVWGEXjh/Gl+dQyXTeRZRsA5k6TCj+42A39j14xSgq6MQJHgbb7M6Jg==} peerDependencies: react: '>=16' dependencies: @@ -237,8 +211,8 @@ packages: swr: 2.2.0(react@18.2.0) dev: false - /@clerk/themes@1.7.5(react@18.2.0): - resolution: {integrity: sha512-kIdCIBSCp6t1saGAzbelcfZi5P+L3+CuVFwSjpulwGTRxeOOs/S8hB4CAHVbTwfxPw5Ms+AnN4dXJKHjSnxPIg==} + /@clerk/themes@1.7.6(react@18.2.0): + resolution: {integrity: sha512-K3nxz3PZX52GmICqKKg/PJ2xtxTOw96Tjxnsb/T4syxMyLmoBBkTnL3Jx96D6zsvSblD+Ujb23z5epSeyQiwtQ==} engines: {node: '>=14'} peerDependencies: react: '>=16' @@ -246,8 +220,8 @@ packages: react: 18.2.0 dev: false - /@clerk/types@3.51.0: - resolution: {integrity: sha512-JCSG2W1nI+zEIyDTGKfQJkvl2Ve5vpR3AxDsyXQMf/aZyXhBT351W1dPUfUW4K9MpLmZ2sB/1gj3UJFaBw9e7g==} + /@clerk/types@3.52.0: + resolution: {integrity: sha512-AzPFyTcVdmMAEmP0Of2DzQ7M/XyBIYL5cNNKYaEZ+0f0T2xxFC4HIDIQqY1GjwDs6MBiHl2nPf2C06pQpOVWbQ==} engines: {node: '>=14'} dependencies: csstype: 3.1.1 @@ -268,7 +242,7 @@ packages: resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} dependencies: '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.7.0 + get-tsconfig: 4.7.2 dev: true /@esbuild/android-arm64@0.18.20: @@ -469,13 +443,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.49.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.49.0 + eslint: 8.50.0 eslint-visitor-keys: 3.4.3 dev: true @@ -491,7 +465,7 @@ packages: ajv: 6.12.6 debug: 4.3.4 espree: 9.6.1 - globals: 13.21.0 + globals: 13.22.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -501,8 +475,8 @@ packages: - supports-color dev: true - /@eslint/js@8.49.0: - resolution: {integrity: sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==} + /@eslint/js@8.50.0: + resolution: {integrity: sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -562,18 +536,18 @@ packages: '@types/pg': 8.6.6 dev: false - /@next/env@13.5.1: - resolution: {integrity: sha512-CIMWiOTyflFn/GFx33iYXkgLSQsMQZV4jB91qaj/TfxGaGOXxn8C1j72TaUSPIyN7ziS/AYG46kGmnvuk1oOpg==} + /@next/env@13.5.2: + resolution: {integrity: sha512-dUseBIQVax+XtdJPzhwww4GetTjlkRSsXeQnisIJWBaHsnxYcN2RGzsPHi58D6qnkATjnhuAtQTJmR1hKYQQPg==} dev: false - /@next/eslint-plugin-next@13.5.1: - resolution: {integrity: sha512-LBlI3iQvlzRE7Y6AfIyHKuM4T6REGmFgweN2tBqEUVEfgxERBLOutV2xckXEp3Y3VbfJBBXKZNfDzs20gHimSg==} + /@next/eslint-plugin-next@13.5.2: + resolution: {integrity: sha512-Ew8DOUerJYGRo8pI84SVwn9wxxx8sH92AanCXSkkLJM2W0RJEWy+BqWSCfrlA/3ZIczEl4l4o4lOeTGBPYfBJg==} dependencies: glob: 7.1.7 dev: true - /@next/swc-darwin-arm64@13.5.1: - resolution: {integrity: sha512-Bcd0VFrLHZnMmJy6LqV1CydZ7lYaBao8YBEdQUVzV8Ypn/l5s//j5ffjfvMzpEQ4mzlAj3fIY+Bmd9NxpWhACw==} + /@next/swc-darwin-arm64@13.5.2: + resolution: {integrity: sha512-7eAyunAWq6yFwdSQliWMmGhObPpHTesiKxMw4DWVxhm5yLotBj8FCR4PXGkpRP2tf8QhaWuVba+/fyAYggqfQg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -581,8 +555,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.5.1: - resolution: {integrity: sha512-uvTZrZa4D0bdWa1jJ7X1tBGIxzpqSnw/ATxWvoRO9CVBvXSx87JyuISY+BWsfLFF59IRodESdeZwkWM2l6+Kjg==} + /@next/swc-darwin-x64@13.5.2: + resolution: {integrity: sha512-WxXYWE7zF1ch8rrNh5xbIWzhMVas6Vbw+9BCSyZvu7gZC5EEiyZNJsafsC89qlaSA7BnmsDXVWQmc+s1feSYbQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -590,8 +564,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.5.1: - resolution: {integrity: sha512-/52ThlqdORPQt3+AlMoO+omicdYyUEDeRDGPAj86ULpV4dg+/GCFCKAmFWT0Q4zChFwsAoZUECLcKbRdcc0SNg==} + /@next/swc-linux-arm64-gnu@13.5.2: + resolution: {integrity: sha512-URSwhRYrbj/4MSBjLlefPTK3/tvg95TTm6mRaiZWBB6Za3hpHKi8vSdnCMw5D2aP6k0sQQIEG6Pzcfwm+C5vrg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -599,8 +573,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.5.1: - resolution: {integrity: sha512-L4qNXSOHeu1hEAeeNsBgIYVnvm0gg9fj2O2Yx/qawgQEGuFBfcKqlmIE/Vp8z6gwlppxz5d7v6pmHs1NB6R37w==} + /@next/swc-linux-arm64-musl@13.5.2: + resolution: {integrity: sha512-HefiwAdIygFyNmyVsQeiJp+j8vPKpIRYDlmTlF9/tLdcd3qEL/UEBswa1M7cvO8nHcr27ZTKXz5m7dkd56/Esg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -608,8 +582,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.5.1: - resolution: {integrity: sha512-QVvMrlrFFYvLtABk092kcZ5Mzlmsk2+SV3xYuAu8sbTuIoh0U2+HGNhVklmuYCuM3DAAxdiMQTNlRQmNH11udw==} + /@next/swc-linux-x64-gnu@13.5.2: + resolution: {integrity: sha512-htGVVroW0tdHgMYwKWkxWvVoG2RlAdDXRO1RQxYDvOBQsaV0nZsgKkw0EJJJ3urTYnwKskn/MXm305cOgRxD2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -617,8 +591,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.5.1: - resolution: {integrity: sha512-bBnr+XuWc28r9e8gQ35XBtyi5KLHLhTbEvrSgcWna8atI48sNggjIK8IyiEBO3KIrcUVXYkldAzGXPEYMnKt1g==} + /@next/swc-linux-x64-musl@13.5.2: + resolution: {integrity: sha512-UBD333GxbHVGi7VDJPPDD1bKnx30gn2clifNJbla7vo5nmBV+x5adyARg05RiT9amIpda6yzAEEUu+s774ldkw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -626,8 +600,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.5.1: - resolution: {integrity: sha512-EQGeE4S5c9v06jje9gr4UlxqUEA+zrsgPi6kg9VwR+dQHirzbnVJISF69UfKVkmLntknZJJI9XpWPB6q0Z7mTg==} + /@next/swc-win32-arm64-msvc@13.5.2: + resolution: {integrity: sha512-Em9ApaSFIQnWXRT3K6iFnr9uBXymixLc65Xw4eNt7glgH0eiXpg+QhjmgI2BFyc7k4ZIjglfukt9saNpEyolWA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -635,8 +609,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.5.1: - resolution: {integrity: sha512-1y31Q6awzofVjmbTLtRl92OX3s+W0ZfO8AP8fTnITcIo9a6ATDc/eqa08fd6tSpFu6IFpxOBbdevOjwYTGx/AQ==} + /@next/swc-win32-ia32-msvc@13.5.2: + resolution: {integrity: sha512-TBACBvvNYU+87X0yklSuAseqdpua8m/P79P0SG1fWUvWDDA14jASIg7kr86AuY5qix47nZLEJ5WWS0L20jAUNw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -644,8 +618,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.5.1: - resolution: {integrity: sha512-+9XBQizy7X/GuwNegq+5QkkxAPV7SBsIwapVRQd9WSvvU20YO23B3bZUpevdabi4fsd25y9RJDDncljy/V54ww==} + /@next/swc-win32-x64-msvc@13.5.2: + resolution: {integrity: sha512-LfTHt+hTL8w7F9hnB3H4nRasCzLD/fP+h4/GUVBTxrkMJOnh/7OZ0XbYDKO/uuWwryJS9kZjhxcruBiYwc5UDw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -762,7 +736,7 @@ packages: resolution: {integrity: sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==} dependencies: '@types/connect': 3.4.36 - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false /@types/cacheable-request@6.0.3: @@ -770,14 +744,14 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.2 '@types/keyv': 3.1.4 - '@types/node': 20.6.3 + '@types/node': 20.6.4 '@types/responselike': 1.0.0 dev: false /@types/connect@3.4.36: resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false /@types/cookies@0.7.7: @@ -786,24 +760,24 @@ packages: '@types/connect': 3.4.36 '@types/express': 4.17.14 '@types/keygrip': 1.0.3 - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false - /@types/eslint@8.44.2: - resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} + /@types/eslint@8.44.3: + resolution: {integrity: sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.2 '@types/json-schema': 7.0.13 dev: true - /@types/estree@1.0.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + /@types/estree@1.0.2: + resolution: {integrity: sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==} dev: true - /@types/express-serve-static-core@4.17.36: - resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} + /@types/express-serve-static-core@4.17.37: + resolution: {integrity: sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 '@types/qs': 6.9.8 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -813,7 +787,7 @@ packages: resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} dependencies: '@types/body-parser': 1.19.3 - '@types/express-serve-static-core': 4.17.36 + '@types/express-serve-static-core': 4.17.37 '@types/qs': 6.9.8 '@types/serve-static': 1.15.2 dev: false @@ -830,12 +804,6 @@ packages: resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} dev: true - /@types/json2csv@5.0.4: - resolution: {integrity: sha512-pH/i1aJnkG/2zpndpozSgu2bu4qrVKFzFjRCcChFa219nFXCeElEyg46xoCjMjGnYbbynRU3k/Qs/XKAFING9Q==} - dependencies: - '@types/node': 20.6.3 - dev: true - /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true @@ -847,7 +815,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false /@types/mime@1.3.2: @@ -861,7 +829,7 @@ packages: /@types/node-fetch@2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 form-data: 3.0.1 dev: false @@ -869,13 +837,13 @@ packages: resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==} dev: false - /@types/node@20.6.3: - resolution: {integrity: sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==} + /@types/node@20.6.4: + resolution: {integrity: sha512-nU6d9MPY0NBUMiE/nXd2IIoC4OLvsLpwAjheoAeuzgvDZA1Cb10QYg+91AF6zQiKWRN5i1m07x6sMe0niBznoQ==} /@types/pg@8.6.6: resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 pg-protocol: 1.6.0 pg-types: 2.2.0 dev: false @@ -903,7 +871,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false /@types/scheduler@0.16.3: @@ -918,7 +886,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false /@types/serve-static@1.15.2: @@ -926,10 +894,10 @@ packages: dependencies: '@types/http-errors': 2.0.2 '@types/mime': 3.0.1 - '@types/node': 20.6.3 + '@types/node': 20.6.4 dev: false - /@typescript-eslint/eslint-plugin@6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.49.0)(typescript@5.2.2): + /@typescript-eslint/eslint-plugin@6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -941,13 +909,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.8.1 - '@typescript-eslint/parser': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) '@typescript-eslint/scope-manager': 6.7.2 - '@typescript-eslint/type-utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/type-utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -958,7 +926,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.7.2(eslint@8.49.0)(typescript@5.2.2): + /@typescript-eslint/parser@6.7.2(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -973,7 +941,7 @@ packages: '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.7.2 debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -987,7 +955,7 @@ packages: '@typescript-eslint/visitor-keys': 6.7.2 dev: true - /@typescript-eslint/type-utils@6.7.2(eslint@8.49.0)(typescript@5.2.2): + /@typescript-eslint/type-utils@6.7.2(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -998,9 +966,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) - '@typescript-eslint/utils': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.7.2(eslint@8.50.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.49.0 + eslint: 8.50.0 ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: @@ -1033,19 +1001,19 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.7.2(eslint@8.49.0)(typescript@5.2.2): + /@typescript-eslint/utils@6.7.2(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.2 '@typescript-eslint/scope-manager': 6.7.2 '@typescript-eslint/types': 6.7.2 '@typescript-eslint/typescript-estree': 6.7.2(typescript@5.2.2) - eslint: 8.49.0 + eslint: 8.50.0 semver: 7.5.4 transitivePeerDependencies: - supports-color @@ -1064,27 +1032,27 @@ packages: resolution: {integrity: sha512-sodjMqcTVBuBMhm8XF8UHL2Uh+47Wtb6j43OmL7aPZmFKj0OJCMv8BB3E8kXOvbdlPmodgTS8eqqaSFEc2VauA==} dev: false - /@upstash/core-analytics@0.0.6(encoding@0.1.13): + /@upstash/core-analytics@0.0.6: resolution: {integrity: sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==} engines: {node: '>=16.0.0'} dependencies: - '@upstash/redis': 1.22.0(encoding@0.1.13) + '@upstash/redis': 1.22.0 transitivePeerDependencies: - encoding dev: false - /@upstash/ratelimit@0.4.4(encoding@0.1.13): + /@upstash/ratelimit@0.4.4: resolution: {integrity: sha512-y3q6cNDdcRQ2MRPRf5UNWBN36IwnZ4kAEkGoH3i6OqdWwz4qlBxNsw4/Rpqn9h93+Nx1cqg5IOq7O2e2zMJY1w==} dependencies: - '@upstash/core-analytics': 0.0.6(encoding@0.1.13) + '@upstash/core-analytics': 0.0.6 transitivePeerDependencies: - encoding dev: false - /@upstash/redis@1.22.0(encoding@0.1.13): + /@upstash/redis@1.22.0: resolution: {integrity: sha512-sXoJDoEqqik0HbrNE7yRWckOySEFsoBxfRdCgOqkc0w6py19ZZG50SpGkDDEUXSnBqP8VgGYXhWAiBpqxrt5oA==} dependencies: - isomorphic-fetch: 3.0.0(encoding@0.1.13) + isomorphic-fetch: 3.0.0 transitivePeerDependencies: - encoding dev: false @@ -1276,14 +1244,14 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false - /autoprefixer@10.4.15(postcss@8.4.30): - resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} + /autoprefixer@10.4.16(postcss@8.4.30): + resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.21.10 + browserslist: 4.21.11 caniuse-lite: 1.0.30001538 fraction.js: 4.3.6 normalize-range: 0.1.2 @@ -1297,8 +1265,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /axe-core@4.8.1: - resolution: {integrity: sha512-9l850jDDPnKq48nbad8SiEelCv4OrUWrKab/cPj0GScVg6cb6NbCCt/Ulk26QEq5jP9NnGr04Bit1BHyV6r5CQ==} + /axe-core@4.8.2: + resolution: {integrity: sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==} engines: {node: '>=4'} dev: true @@ -1365,15 +1333,15 @@ packages: fill-range: 7.0.1 dev: true - /browserslist@4.21.10: - resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} + /browserslist@4.21.11: + resolution: {integrity: sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: caniuse-lite: 1.0.30001538 - electron-to-chromium: 1.4.525 + electron-to-chromium: 1.4.528 node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.10) + update-browserslist-db: 1.0.13(browserslist@4.21.11) dev: false /buffer-from@1.1.2: @@ -1570,13 +1538,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.15 - dev: false - /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -1626,8 +1587,8 @@ packages: type: 1.2.0 dev: true - /daisyui@3.7.6: - resolution: {integrity: sha512-7MsD3J1/LpRkxvkjm0OQBjmVhzEhQdi6lTfEP2Wrvgl/fjKst4KmfkM39g0iInroaFq1fEJbB/vobGDqFgXoYw==} + /daisyui@3.7.7: + resolution: {integrity: sha512-2/nFdW/6R9MMnR8tTm07jPVyPaZwpUSkVsFAADb7Oq8N2Ynbls57laDdNqxTCUmn0QvcZi01TKl8zQbAwRfw1w==} engines: {node: '>=16.9.0'} dependencies: colord: 2.9.3 @@ -1865,19 +1826,14 @@ packages: pg: 8.11.3 dev: false - /electron-to-chromium@1.4.525: - resolution: {integrity: sha512-GIZ620hDK4YmIqAWkscG4W6RwY6gOx1y5J6f4JUQwctiJrqH2oxZYU4mXHi35oV32tr630UcepBzSBGJ/WYcZA==} + /electron-to-chromium@1.4.528: + resolution: {integrity: sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==} dev: false /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true - /encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - dependencies: - iconv-lite: 0.6.3 - /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: @@ -2069,8 +2025,8 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-next@13.5.1(eslint@8.49.0)(typescript@5.2.2): - resolution: {integrity: sha512-+8xIIWtD+iFwHfXgmXRGn05BuNIu/RAGcz6kI4wsJTPrE/1WtWKv2o0l+GbQ6wRaC+cbBV8+QnFAOf18aJiWrg==} + /eslint-config-next@13.5.2(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-kCF7k7fHBtFtxfP6J6AP6Mo0vW3CrFeoIuoZ7NHGIvLFc/RUaIspJ6inO/R33zE1o9t/lbJgTnsqnRB++sxCUQ==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 typescript: '>=3.3.1' @@ -2078,16 +2034,16 @@ packages: typescript: optional: true dependencies: - '@next/eslint-plugin-next': 13.5.1 + '@next/eslint-plugin-next': 13.5.2 '@rushstack/eslint-patch': 1.4.0 - '@typescript-eslint/parser': 6.7.2(eslint@8.49.0)(typescript@5.2.2) - eslint: 8.49.0 + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) + eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.49.0) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0) - eslint-plugin-jsx-a11y: 6.7.1(eslint@8.49.0) - eslint-plugin-react: 7.33.2(eslint@8.49.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.49.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + eslint-plugin-jsx-a11y: 6.7.1(eslint@8.50.0) + eslint-plugin-react: 7.33.2(eslint@8.50.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.50.0) typescript: 5.2.2 transitivePeerDependencies: - eslint-import-resolver-webpack @@ -2104,8 +2060,8 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.49.0): - resolution: {integrity: sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==} + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2113,11 +2069,11 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.15.0 - eslint: 8.49.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0) + eslint: 8.50.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) fast-glob: 3.3.1 - get-tsconfig: 4.7.0 + get-tsconfig: 4.7.2 is-core-module: 2.13.0 is-glob: 4.0.3 transitivePeerDependencies: @@ -2127,7 +2083,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -2148,16 +2104,16 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) debug: 3.2.7 - eslint: 8.49.0 + eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.49.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0): + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -2167,16 +2123,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.7.2(eslint@8.49.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.2(eslint@8.50.0)(typescript@5.2.2) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.49.0 + eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.49.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -2192,7 +2148,7 @@ packages: - supports-color dev: true - /eslint-plugin-jsx-a11y@6.7.1(eslint@8.49.0): + /eslint-plugin-jsx-a11y@6.7.1(eslint@8.50.0): resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} engines: {node: '>=4.0'} peerDependencies: @@ -2203,11 +2159,11 @@ packages: array-includes: 3.1.7 array.prototype.flatmap: 1.3.2 ast-types-flow: 0.0.7 - axe-core: 4.8.1 + axe-core: 4.8.2 axobject-query: 3.2.1 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 8.49.0 + eslint: 8.50.0 has: 1.0.3 jsx-ast-utils: 3.3.5 language-tags: 1.0.5 @@ -2217,16 +2173,16 @@ packages: semver: 6.3.1 dev: true - /eslint-plugin-react-hooks@4.6.0(eslint@8.49.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.50.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.49.0 + eslint: 8.50.0 dev: true - /eslint-plugin-react@7.33.2(eslint@8.49.0): + /eslint-plugin-react@7.33.2(eslint@8.50.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} peerDependencies: @@ -2237,7 +2193,7 @@ packages: array.prototype.tosorted: 1.1.2 doctrine: 2.1.0 es-iterator-helpers: 1.0.15 - eslint: 8.49.0 + eslint: 8.50.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.5 minimatch: 3.1.2 @@ -2264,15 +2220,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.49.0: - resolution: {integrity: sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==} + /eslint@8.50.0: + resolution: {integrity: sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.49.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@eslint-community/regexpp': 4.8.1 '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.49.0 + '@eslint/js': 8.50.0 '@humanwhocodes/config-array': 0.11.11 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2291,7 +2247,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.21.0 + globals: 13.22.0 graphemer: 1.4.0 ignore: 5.2.4 imurmurhash: 0.1.4 @@ -2514,8 +2470,8 @@ packages: get-intrinsic: 1.2.1 dev: true - /get-tsconfig@4.7.0: - resolution: {integrity: sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==} + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} dependencies: resolve-pkg-maps: 1.0.0 dev: true @@ -2586,8 +2542,8 @@ packages: once: 1.4.0 dev: true - /globals@13.21.0: - resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + /globals@13.22.0: + resolution: {integrity: sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -2704,12 +2660,6 @@ packages: resolve-alpn: 1.2.1 dev: false - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -2924,11 +2874,6 @@ packages: get-intrinsic: 1.2.1 dev: true - /is-what@4.1.15: - resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} - engines: {node: '>=12.13'} - dev: false - /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true @@ -2937,10 +2882,10 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /isomorphic-fetch@3.0.0(encoding@0.1.13): + /isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} dependencies: - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0 whatwg-fetch: 3.6.19 transitivePeerDependencies: - encoding @@ -3202,8 +3147,8 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true - /next@13.5.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GIudNR7ggGUZoIL79mSZcxbXK9f5pwAIPZxEM8+j2yLqv5RODg4TkmUlaKSYVqE1bPQueamXSqdC3j7axiTSEg==} + /next@13.5.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-vog4UhUaMYAzeqfiAAmgB/QWLW7p01/sg+2vn6bqc/CxHFYizMzLv6gjxKzl31EVFkfl/F+GbxlKizlkTE9RdA==} engines: {node: '>=16.14.0'} hasBin: true peerDependencies: @@ -3217,7 +3162,7 @@ packages: sass: optional: true dependencies: - '@next/env': 13.5.1 + '@next/env': 13.5.2 '@swc/helpers': 0.5.2 busboy: 1.6.0 caniuse-lite: 1.0.30001538 @@ -3228,27 +3173,27 @@ packages: watchpack: 2.4.0 zod: 3.21.4 optionalDependencies: - '@next/swc-darwin-arm64': 13.5.1 - '@next/swc-darwin-x64': 13.5.1 - '@next/swc-linux-arm64-gnu': 13.5.1 - '@next/swc-linux-arm64-musl': 13.5.1 - '@next/swc-linux-x64-gnu': 13.5.1 - '@next/swc-linux-x64-musl': 13.5.1 - '@next/swc-win32-arm64-msvc': 13.5.1 - '@next/swc-win32-ia32-msvc': 13.5.1 - '@next/swc-win32-x64-msvc': 13.5.1 + '@next/swc-darwin-arm64': 13.5.2 + '@next/swc-darwin-x64': 13.5.2 + '@next/swc-linux-arm64-gnu': 13.5.2 + '@next/swc-linux-arm64-musl': 13.5.2 + '@next/swc-linux-x64-gnu': 13.5.2 + '@next/swc-linux-x64-musl': 13.5.2 + '@next/swc-win32-arm64-msvc': 13.5.2 + '@next/swc-win32-ia32-msvc': 13.5.2 + '@next/swc-win32-x64-msvc': 13.5.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros dev: false - /nextjs-cors@2.1.2(next@13.5.1): + /nextjs-cors@2.1.2(next@13.5.2): resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==} peerDependencies: next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 dependencies: cors: 2.8.5 - next: 13.5.1(react-dom@18.2.0)(react@18.2.0) + next: 13.5.2(react-dom@18.2.0)(react@18.2.0) dev: false /no-case@3.0.4: @@ -3273,7 +3218,7 @@ packages: resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==} dev: false - /node-fetch@2.7.0(encoding@0.1.13): + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -3282,7 +3227,6 @@ packages: encoding: optional: true dependencies: - encoding: 0.1.13 whatwg-url: 5.0.0 dev: false @@ -3867,9 +3811,6 @@ packages: is-regex: 1.1.4 dev: true - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -4113,13 +4054,6 @@ packages: ts-interface-checker: 0.1.13 dev: true - /superjson@1.13.1: - resolution: {integrity: sha512-AVH2eknm9DEd3qvxM4Sq+LTCkSXE2ssfh1t11MHMXyYXFQyQ1HLgVvV+guLTsaQnJU3gnaVo34TohHPulY/wLg==} - engines: {node: '>=10'} - dependencies: - copy-anything: 3.0.5 - dev: false - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4132,22 +4066,22 @@ packages: engines: {node: '>= 0.4'} dev: true - /svix-fetch@3.0.0(encoding@0.1.13): + /svix-fetch@3.0.0: resolution: {integrity: sha512-rcADxEFhSqHbraZIsjyZNh4TF6V+koloX1OzZ+AQuObX9mZ2LIMhm1buZeuc5BIZPftZpJCMBsSiBaeszo9tRw==} dependencies: - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0 whatwg-fetch: 3.6.19 transitivePeerDependencies: - encoding dev: false - /svix@1.12.0(encoding@0.1.13): + /svix@1.12.0: resolution: {integrity: sha512-6iIYxR/BoVEBDokTcvApcqq4M2szpFOwV8NwuYyvf4cRpz7JEV1KFQDMkNxEi9aR1idkhK4bSmeQYIx1HyGiJw==} dependencies: '@stablelib/base64': 1.0.1 es6-promise: 4.2.8 fast-sha256: 1.3.0 - svix-fetch: 3.0.0(encoding@0.1.13) + svix-fetch: 3.0.0 url-parse: 1.5.10 transitivePeerDependencies: - encoding @@ -4402,13 +4336,13 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /update-browserslist-db@1.0.11(browserslist@4.21.10): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + /update-browserslist-db@1.0.13(browserslist@4.21.11): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.10 + browserslist: 4.21.11 escalade: 3.1.1 picocolors: 1.0.0 dev: false @@ -4561,22 +4495,6 @@ packages: utf-8-validate: 6.0.3 dev: false - /ws@8.14.2(bufferutil@4.0.7)(utf-8-validate@6.0.3): - resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dependencies: - bufferutil: 4.0.7 - utf-8-validate: 6.0.3 - dev: true - /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/server/actions/room.ts b/src/server/actions/room.ts deleted file mode 100644 index 75c6864..0000000 --- a/src/server/actions/room.ts +++ /dev/null @@ -1,230 +0,0 @@ -"use server"; - -import { createId } from "@paralleldrive/cuid2"; -import { db } from "../db"; -import { logs, rooms, votes } from "../schema"; -import { auth } from "@clerk/nextjs"; -import { fetchCache, invalidateCache, setCache } from "../redis"; -import { publishToChannel } from "../ably"; -import { EventTypes } from "@/utils/types"; -import { eq } from "drizzle-orm"; - -/** - * Creates a new room. - * - * @param {string} name - The name of the room. - * @returns {Promise} - A promise that resolves to a boolean indicating the success of room creation. - */ -export const createRoom = async (name: string) => { - const { userId } = auth(); - - if (!userId) { - return false; - } - - const room = await db - .insert(rooms) - .values({ - id: `room_${createId()}`, - userId, - roomName: name, - storyName: "First Story!", - scale: "0.5,1,2,3,5,8", - visible: false, - }) - .returning(); - - const success = room.length > 0; - if (room) { - await invalidateCache(`kv_roomlist_${userId}`); - - await publishToChannel( - `${userId}`, - EventTypes.ROOM_LIST_UPDATE, - JSON.stringify(room) - ); - } - return success; -}; - -/** - * Deletes a room with the specified ID. - * - * @param {string} id - The ID of the room to delete. - * @returns {Promise} - A promise that resolves to a boolean indicating the success of room deletion. - */ -export const deleteRoom = async (id: string) => { - const { userId } = auth(); - - if (!userId) { - return false; - } - - const deletedRoom = await db - .delete(rooms) - .where(eq(rooms.id, id)) - .returning(); - - const success = deletedRoom.length > 0; - - if (success) { - await publishToChannel( - `${userId}`, - EventTypes.ROOM_LIST_UPDATE, - JSON.stringify(deletedRoom) - ); - - await publishToChannel( - `${id}`, - EventTypes.ROOM_UPDATE, - JSON.stringify(deletedRoom) - ); - - await invalidateCache(`kv_roomlist_${userId}`); - - await publishToChannel( - `${userId}`, - EventTypes.ROOM_LIST_UPDATE, - JSON.stringify(deletedRoom) - ); - } - - return success; -}; - -/** - * Retrieves a room with the specified ID. - * - * @param {string} id - The ID of the room to retrieve. - * @returns {Promise} - A promise that resolves to the retrieved room object or null if not found. - */ -export const getRoom = async (id: string) => { - const { userId } = auth(); - - if (!userId) { - return null; - } - - const roomFromDb = await db.query.rooms.findFirst({ - where: eq(rooms.id, id), - with: { - logs: true, - }, - }); - return roomFromDb || null; -}; - -/** - * Retrieves a list of rooms. - * - * @returns {Promise} - A promise that resolves to an array of room objects or null if not found. - */ -export const getRooms = async () => { - const { userId } = auth(); - - if (!userId) { - return null; - } - - const cachedResult = await fetchCache< - { - id: string; - createdAt: Date; - roomName: string; - }[] - >(`kv_roomlist_${userId}`); - - if (cachedResult) { - return cachedResult; - } else { - const roomList = await db.query.rooms.findMany({ - where: eq(rooms.userId, userId), - }); - - await setCache(`kv_roomlist_${userId}`, roomList); - - return roomList; - } -}; - -/** - * Sets the properties of a room. - * - * @param {string} name - The new name of the room. - * @param {boolean} visible - Indicates if the room is visible. - * @param {string} scale - The scale values for the room. - * @param {string} roomId - The ID of the room to update. - * @param {boolean} reset - Indicates whether to reset room data. - * @param {boolean} log - Indicates whether to log changes. - * @returns {Promise} - A promise that resolves to a boolean indicating the success of the room update. - */ -export const setRoom = async ( - name: string, - visible: boolean, - scale: string, - roomId: string, - reset: boolean, - log: boolean -) => { - const { userId } = auth(); - - if (!userId) { - return false; - } - - if (reset) { - if (log) { - const oldRoom = await db.query.rooms.findFirst({ - where: eq(rooms.id, roomId), - with: { - votes: true, - logs: true, - }, - }); - - oldRoom && - (await db.insert(logs).values({ - id: `log_${createId()}`, - userId: userId, - roomId: roomId, - scale: oldRoom.scale, - votes: oldRoom.votes.map((vote) => { - return { - name: vote.userId, - value: vote.value, - }; - }), - roomName: oldRoom.roomName, - storyName: oldRoom.storyName, - })); - } - - await db.delete(votes).where(eq(votes.roomId, roomId)); - - await invalidateCache(`kv_votes_${roomId}`); - } - - const newRoom = await db - .update(rooms) - .set({ - storyName: name, - visible: visible, - scale: [...new Set(scale.split(","))] - .filter((item) => item !== "") - .toString(), - }) - .where(eq(rooms.id, roomId)) - .returning(); - - const success = newRoom.length > 0; - - if (success) { - await publishToChannel( - `${roomId}`, - EventTypes.ROOM_UPDATE, - JSON.stringify(newRoom) - ); - } - - return success; -}; diff --git a/src/server/actions/vote.ts b/src/server/actions/vote.ts deleted file mode 100644 index 03ba2ba..0000000 --- a/src/server/actions/vote.ts +++ /dev/null @@ -1,94 +0,0 @@ -"use server"; - -import { createId } from "@paralleldrive/cuid2"; -import { db } from "../db"; -import { votes } from "../schema"; -import { auth } from "@clerk/nextjs"; -import { fetchCache, invalidateCache, setCache } from "../redis"; -import { publishToChannel } from "../ably"; -import { EventTypes } from "@/utils/types"; -import { eq } from "drizzle-orm"; - -/** - * Retrieves votes for a specific room. - * - * @param {string} roomId - The ID of the room for which votes are retrieved. - * @returns {Promise} - A promise that resolves to an array of vote objects or null if not found. - */ -export const getVotes = async (roomId: string) => { - const { userId } = auth(); - - if (!userId) { - return null; - } - - const cachedResult = await fetchCache< - { - id: string; - value: string; - created_at: Date; - userId: string; - roomId: string; - }[] - >(`kv_votes_${roomId}`); - - if (cachedResult) { - return cachedResult; - } else { - const votesByRoomId = await db.query.votes.findMany({ - where: eq(votes.roomId, roomId), - }); - - await setCache(`kv_votes_${roomId}`, votesByRoomId); - - return votesByRoomId; - } -}; - -/** - * Sets a vote value for a room. - * - * @param {string} value - The value of the vote. - * @param {string} roomId - The ID of the room for which the vote is being set. - * @returns {Promise} - A promise that resolves to a boolean indicating the success of the vote setting. - */ -export const setVote = async (value: string, roomId: string) => { - const { userId } = auth(); - - if (!userId) { - return false; - } - - const upsertResult = await db - .insert(votes) - .values({ - id: `vote_${createId()}`, - value: value, - userId: userId, - roomId: roomId, - }) - .onConflictDoUpdate({ - target: [votes.userId, votes.roomId], - set: { - value: value, - userId: userId, - roomId: roomId, - }, - }); - - const success = upsertResult.rowCount > 0; - - if (success) { - await invalidateCache(`kv_votes_${roomId}`); - - await publishToChannel(`${roomId}`, EventTypes.VOTE_UPDATE, value); - - await publishToChannel( - `stats`, - EventTypes.STATS_UPDATE, - JSON.stringify(success) - ); - } - - return success; -}; diff --git a/src/utils/types.ts b/src/utils/types.ts deleted file mode 100644 index f55e5e1..0000000 --- a/src/utils/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -type BetterEnum = T[keyof T]; - -export const EventTypes = { - ROOM_LIST_UPDATE: "room.list.update", - ROOM_UPDATE: "room.update", - VOTE_UPDATE: "vote.update", - STATS_UPDATE: "stats.update", -} as const; -export type EventType = BetterEnum; - -export interface PresenceItem { - name: string; - image: string; - client_id: string; - isAdmin: boolean; - isVIP: boolean; -} diff --git a/tsconfig.json b/tsconfig.json index 6b6ff7f..c647dfb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ "noUncheckedIndexedAccess": true, "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./app/*"] }, "plugins": [ {