From 7299f4f7fb5b1c7b0bbcb945573c74e9bc49f9d4 Mon Sep 17 00:00:00 2001 From: Ryan Pandya Date: Sun, 15 Dec 2024 18:40:38 -0800 Subject: [PATCH] Dockerfile and database download + upload --- .dockerignore | 2 +- Dockerfile | 51 ++++++ apps/web/app/api/db/download/route.ts | 23 +++ apps/web/app/api/db/upload/route.ts | 63 +++++++ apps/web/app/settings/database/page.tsx | 9 + .../components/settings/DatabaseSettings.tsx | 63 +++++++ .../web/components/settings/sidebar/items.tsx | 5 + apps/web/next-env.d.ts | 2 +- apps/web/package.json | 2 + docker/Dockerfile | 141 ---------------- pnpm-lock.yaml | 156 +++++++++++------- 11 files changed, 314 insertions(+), 203 deletions(-) create mode 100644 Dockerfile create mode 100644 apps/web/app/api/db/download/route.ts create mode 100644 apps/web/app/api/db/upload/route.ts create mode 100644 apps/web/app/settings/database/page.tsx create mode 100644 apps/web/components/settings/DatabaseSettings.tsx delete mode 100644 docker/Dockerfile diff --git a/.dockerignore b/.dockerignore index 6572d8b..dae7cd3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -ockerfile +Dockerfile .dockerignore node_modules npm-debug.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd70d24 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM node:18-alpine AS base + +FROM base AS builder +RUN apk update +RUN apk add --no-cache libc6-compat +# Set working directory +WORKDIR /app +# Replace with the major version installed in your repository. For example: +# RUN yarn global add turbo@^2 + +RUN corepack enable + +RUN pnpm install turbo@^2.2.3 +COPY . . + +# Generate a partial monorepo with a pruned lockfile for a target workspace. +# Assuming "web" is the name entered in the project's package.json: { name: "web" } +RUN pnpm turbo prune @lifetracker/web --docker + +# Add lockfile and package.json's of isolated subworkspace +FROM base AS installer +RUN apk update +RUN apk add --no-cache libc6-compat +WORKDIR /app + +RUN corepack enable + +# First install the dependencies (as they change less often) +COPY --from=builder /app/out/json/ . +RUN pnpm install + +# Build the project +COPY --from=builder /app/out/full/ . +RUN pnpm turbo run build + +FROM base AS runner +WORKDIR /app + +# Don't run production as root +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +USER nextjs + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ +COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public + +EXPOSE 3000 +CMD node apps/web/server.js diff --git a/apps/web/app/api/db/download/route.ts b/apps/web/app/api/db/download/route.ts new file mode 100644 index 0000000..68186b9 --- /dev/null +++ b/apps/web/app/api/db/download/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; +import serverConfig from '@lifetracker/shared/config'; + +const DB_PATH = path.join(serverConfig.dataDir, 'lifetracker.db'); + +export async function GET(req: NextRequest) { + try { + // Read the production database file + const dbFile = fs.readFileSync(DB_PATH); + return new NextResponse(dbFile, { + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': 'attachment; filename="lifetracker.db"' + + } + }); + } catch (error) { + console.error('Error downloading the database:', error); + return NextResponse.json({ message: 'Error processing download.' }, { status: 500 }); + } +} diff --git a/apps/web/app/api/db/upload/route.ts b/apps/web/app/api/db/upload/route.ts new file mode 100644 index 0000000..e8a28dd --- /dev/null +++ b/apps/web/app/api/db/upload/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; +import serverConfig from '@lifetracker/shared/config'; + +// Define paths for uploaded files and the production DB +const UPLOAD_DIR = path.join(process.cwd(), 'uploads'); +const DB_PATH = path.join(serverConfig.dataDir, 'lifetracker.db'); + +// Ensure upload directory exists +if (!fs.existsSync(UPLOAD_DIR)) { + fs.mkdirSync(UPLOAD_DIR, { recursive: true }); +} + +export async function POST(req: NextRequest) { + try { + const contentType = req.headers.get('content-type') || ''; + if (!contentType.includes('multipart/form-data')) { + return NextResponse.json({ message: 'Invalid content type. Please upload a file.' }, { status: 400 }); + } + + // Create a writable stream for saving the uploaded file + const formBoundary = contentType.split('boundary=')[1]; + const formData = req.body; + + // Parse and save the uploaded file + const chunks: Buffer[] = []; + for await (const chunk of formData) { + chunks.push(chunk); + } + + const bodyBuffer = Buffer.concat(chunks); + const start = bodyBuffer.indexOf(`\r\n\r\n`) + 4; + const end = bodyBuffer.indexOf(`\r\n--${formBoundary}`, start); + const fileContent = bodyBuffer.slice(start, end); + + // Save the uploaded file to the upload directory + const uploadedFilePath = path.join(UPLOAD_DIR, 'uploaded.db'); + fs.writeFileSync(uploadedFilePath, fileContent); + + // Validate the uploaded file extension + if (!uploadedFilePath.endsWith('.db')) { + return NextResponse.json({ message: 'Invalid file type. Please upload a .db file.' }, { status: 400 }); + } + + // Backup the current production database + const backupPath = path.join( + serverConfig.dataDir, + `backup_${Date.now()}.db` + ); + if (fs.existsSync(DB_PATH)) { + fs.copyFileSync(DB_PATH, backupPath); + } + + // Replace the production database with the uploaded file + fs.renameSync(uploadedFilePath, DB_PATH); + + return NextResponse.json({ message: 'Database uploaded and replaced successfully!' }, { status: 200 }); + } catch (error) { + console.error('Error handling the uploaded file:', error); + return NextResponse.json({ message: 'Error processing upload.' }, { status: 500 }); + } +} diff --git a/apps/web/app/settings/database/page.tsx b/apps/web/app/settings/database/page.tsx new file mode 100644 index 0000000..3022097 --- /dev/null +++ b/apps/web/app/settings/database/page.tsx @@ -0,0 +1,9 @@ +import DatabaseSettings from "@/components/settings/DatabaseSettings"; + +export default async function DatabasePage() { + return ( +
+ +
+ ); +} diff --git a/apps/web/components/settings/DatabaseSettings.tsx b/apps/web/components/settings/DatabaseSettings.tsx new file mode 100644 index 0000000..6bafeb1 --- /dev/null +++ b/apps/web/components/settings/DatabaseSettings.tsx @@ -0,0 +1,63 @@ +"use client"; // Mark as client component + +import { Separator } from "@radix-ui/react-dropdown-menu"; +import { useState } from "react"; + +export default function DatabaseSettings() { + const [uploadStatus, setUploadStatus] = useState(null); + + // Handle form submission + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); // Prevent default form submission + + const formData = new FormData(event.currentTarget); + + try { + const response = await fetch("/api/db/upload", { + method: "POST", + body: formData, + }); + + if (response.ok) { + setUploadStatus("Database uploaded successfully!"); + } else { + const error = await response.json(); + setUploadStatus(error.message || "An error occurred during upload."); + } + } catch (error) { + console.error("Error uploading the database:", error); + setUploadStatus("An error occurred during upload."); + } + }; + + return ( + <>
+
+ Upload SQLite Database +
+
+
+
+ + +
+ {uploadStatus &&

{uploadStatus}

} +
+
+
+
+
+ Download SQLite Database +
+ +
+ + ); +} + diff --git a/apps/web/components/settings/sidebar/items.tsx b/apps/web/components/settings/sidebar/items.tsx index 8335114..ebe9928 100644 --- a/apps/web/components/settings/sidebar/items.tsx +++ b/apps/web/components/settings/sidebar/items.tsx @@ -45,4 +45,9 @@ export const settingsSidebarItems: { icon: , path: "/settings/api-keys", }, + { + name: "Database", + icon: , + path: "/settings/database", + } ]; diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 40c3d68..1b3be08 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index b8c24b6..8cd363a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,7 @@ "@trpc/client": "11.0.0-next-beta.308", "@trpc/react-query": "11.0.0-next-beta.308", "@trpc/server": "11.0.0-next-beta.308", + "@types/formidable": "^3.4.5", "better-sqlite3": "^11.3.0", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.0", @@ -55,6 +56,7 @@ "dayjs": "^1.11.10", "drizzle-orm": "^0.33.0", "fastest-levenshtein": "^1.0.16", + "formidable": "^3.5.2", "lucide-react": "latest", "next": "latest", "next-auth": "^4.24.5", diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index a2c5721..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,141 +0,0 @@ -################# Base Builder ############## -FROM node:22-alpine AS base - -WORKDIR /app -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable - -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat make g++ py3-pip linux-headers - -COPY . . -ENV NEXT_TELEMETRY_DISABLED 1 -ENV PUPPETEER_SKIP_DOWNLOAD true -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile - -# Build the db migration script -RUN cd packages/db && \ - pnpm dlx @vercel/ncc build migrate.ts -o /db_migrations && \ - cp -R migrations /db_migrations - - -# Compile the web app -RUN (cd apps/web && pnpm exec next build --experimental-build-mode compile) - -# Build the worker code -# RUN --mount=type=cache,id=pnpm_workers,target=/pnpm/store pnpm deploy --node-linker=isolated --filter @lifetracker/workers --prod /prod/workers - -# Build the cli -RUN (cd apps/cli && pnpm build) - -################# The All-in-one builder ############## - -FROM node:22-alpine AS aio_builder -LABEL org.opencontainers.image.source="https://git.ryanpandya.com/ryan/lifetracker" -WORKDIR /app - -ARG SERVER_VERSION=nightly -ENV SERVER_VERSION=${SERVER_VERSION} - -USER root - -ENV PORT 3000 -ENV HOSTNAME "0.0.0.0" -EXPOSE 3000 - -###################### -# Prepare s6-overlay -###################### -ARG S6_OVERLAY_VERSION=3.2.0.0 -ARG TARGETARCH - -ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp -RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz \ - && case ${TARGETARCH} in \ - "amd64") S6_ARCH=x86_64 ;; \ - "arm64") S6_ARCH=aarch64 ;; \ - esac \ - && echo https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz -O /tmp/s6-overlay-${S6_ARCH}.tar.xz \ - && wget https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz -O /tmp/s6-overlay-${S6_ARCH}.tar.xz \ - && tar -C / -Jxpf /tmp/s6-overlay-${S6_ARCH}.tar.xz \ - && rm -f /tmp/s6-overlay-${S6_ARCH}.tar.xz - -# Copy the s6-overlay config -COPY --chmod=755 ./docker/root/etc/s6-overlay /etc/s6-overlay - -###################### -# Install runtime deps -###################### -# RUN apk add --no-cache monolith yt-dlp - -###################### -# Prepare the web app -###################### - -ENV NODE_ENV production -ENV NEXT_TELEMETRY_DISABLED 1 - -COPY --from=base --chown=node:node /app/apps/web/.next/standalone ./ -COPY --from=base /app/apps/web/public ./apps/web/public -COPY --from=base /db_migrations /db_migrations - -# Set the correct permission for prerender cache -RUN mkdir -p ./apps/web/.next && chown node:node ./apps/web/.next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=base --chown=node:node /app/apps/web/.next/static ./apps/web/.next/static - -###################### -# Prepare the workers app -###################### -COPY --from=base /prod/workers /app/apps/workers -RUN corepack enable && corepack pack - -ENTRYPOINT ["/init"] - -################# The AIO ############## - -FROM aio_builder AS aio - -RUN touch /etc/s6-overlay/s6-rc.d/user/contents.d/init-db-migration \ - /etc/s6-overlay/s6-rc.d/user/contents.d/svc-web \ - /etc/s6-overlay/s6-rc.d/user/contents.d/svc-workers - -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1 - -################# The web container ############## - -FROM aio_builder AS web - -RUN touch /etc/s6-overlay/s6-rc.d/user/contents.d/init-db-migration \ - /etc/s6-overlay/s6-rc.d/user/contents.d/svc-web -ENV USING_LEGACY_SEPARATE_CONTAINERS=true - -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1 - -################# The workers container ############## - -FROM aio_builder AS workers - -# In the current implemtation, the workers assume the migration -# is done for them. -RUN rm /etc/s6-overlay/s6-rc.d/svc-workers/dependencies.d/init-db-migration \ - && touch /etc/s6-overlay/s6-rc.d/user/contents.d/svc-workers -ENV USING_LEGACY_SEPARATE_CONTAINERS=true - -################# The cli ############## - -FROM node:22-alpine AS cli -LABEL org.opencontainers.image.source="https://github.com/hoarder-app/hoarder" -WORKDIR /app - -COPY --from=base /app/apps/cli/dist/index.mjs apps/cli/index.mjs - -WORKDIR /app/apps/cli - -ARG SERVER_VERSION=nightly -ENV SERVER_VERSION=${SERVER_VERSION} - -ENTRYPOINT ["node", "index.mjs"] \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8911c0..5583613 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -241,6 +241,9 @@ importers: '@trpc/server': specifier: 11.0.0-next-beta.308 version: 11.0.0-next-beta.308 + '@types/formidable': + specifier: ^3.4.5 + version: 3.4.5 better-sqlite3: specifier: ^11.3.0 version: 11.5.0 @@ -271,18 +274,21 @@ importers: fastest-levenshtein: specifier: ^1.0.16 version: 1.0.16 + formidable: + specifier: ^3.5.2 + version: 3.5.2 lucide-react: specifier: latest version: 0.468.0(react@18.3.1) next: specifier: latest - version: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.5 - version: 4.24.10(next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.24.10(next@15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-pwa: specifier: ^5.6.0 - version: 5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.95.0) + version: 5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.95.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3108,56 +3114,56 @@ packages: '@microsoft/tsdoc@0.14.2': resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} - '@next/env@15.0.3': - resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} + '@next/env@15.1.0': + resolution: {integrity: sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w==} '@next/eslint-plugin-next@14.2.6': resolution: {integrity: sha512-d3+p4AjIYmhqzYHhhmkRYYN6ZU35TwZAKX08xKRfnHkz72KhWL2kxMFsDptpZs5e8bBGdepn7vn1+9DaF8iX+A==} - '@next/swc-darwin-arm64@15.0.3': - resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + '@next/swc-darwin-arm64@15.1.0': + resolution: {integrity: sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.0.3': - resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + '@next/swc-darwin-x64@15.1.0': + resolution: {integrity: sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.0.3': - resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} + '@next/swc-linux-arm64-gnu@15.1.0': + resolution: {integrity: sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.3': - resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + '@next/swc-linux-arm64-musl@15.1.0': + resolution: {integrity: sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.3': - resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} + '@next/swc-linux-x64-gnu@15.1.0': + resolution: {integrity: sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.3': - resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} + '@next/swc-linux-x64-musl@15.1.0': + resolution: {integrity: sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.0.3': - resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} + '@next/swc-win32-arm64-msvc@15.1.0': + resolution: {integrity: sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.3': - resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} + '@next/swc-win32-x64-msvc@15.1.0': + resolution: {integrity: sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4002,8 +4008,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.13': - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} @@ -4149,6 +4155,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/formidable@3.4.5': + resolution: {integrity: sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -5980,6 +5989,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -6887,6 +6899,9 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -7277,6 +7292,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -8880,16 +8899,16 @@ packages: react: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18 - next@15.0.3: - resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + next@15.1.0: + resolution: {integrity: sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -16062,34 +16081,34 @@ snapshots: '@microsoft/tsdoc@0.14.2': {} - '@next/env@15.0.3': {} + '@next/env@15.1.0': {} '@next/eslint-plugin-next@14.2.6': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.0.3': + '@next/swc-darwin-arm64@15.1.0': optional: true - '@next/swc-darwin-x64@15.0.3': + '@next/swc-darwin-x64@15.1.0': optional: true - '@next/swc-linux-arm64-gnu@15.0.3': + '@next/swc-linux-arm64-gnu@15.1.0': optional: true - '@next/swc-linux-arm64-musl@15.0.3': + '@next/swc-linux-arm64-musl@15.1.0': optional: true - '@next/swc-linux-x64-gnu@15.0.3': + '@next/swc-linux-x64-gnu@15.1.0': optional: true - '@next/swc-linux-x64-musl@15.0.3': + '@next/swc-linux-x64-musl@15.1.0': optional: true - '@next/swc-win32-arm64-msvc@15.0.3': + '@next/swc-win32-arm64-msvc@15.1.0': optional: true - '@next/swc-win32-x64-msvc@15.0.3': + '@next/swc-win32-x64-msvc@15.1.0': optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -16142,7 +16161,7 @@ snapshots: is-glob: 4.0.3 open: 9.1.0 picocolors: 1.1.1 - tslib: 2.6.2 + tslib: 2.8.1 '@pnpm/config.env-replace@1.1.0': {} @@ -17129,7 +17148,7 @@ snapshots: '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.13': + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -17323,6 +17342,10 @@ snapshots: '@types/qs': 6.9.16 '@types/serve-static': 1.15.7 + '@types/formidable@3.4.5': + dependencies: + '@types/node': 20.11.24 + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 @@ -19579,6 +19602,11 @@ snapshots: dependencies: dequal: 2.0.3 + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -19653,7 +19681,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.8.1 dot-prop@6.0.1: dependencies: @@ -20050,7 +20078,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.33.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -20104,7 +20132,7 @@ snapshots: enhanced-resolve: 5.15.0 eslint: 8.57.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.1 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -20143,7 +20171,7 @@ snapshots: eslint: 8.57.0 ignore: 5.3.1 - eslint-plugin-import@2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.0(@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 @@ -20843,6 +20871,12 @@ snapshots: format@0.2.2: {} + formidable@3.5.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 2.0.0 + once: 1.4.0 + forwarded@0.2.0: {} fraction.js@4.3.7: {} @@ -21346,6 +21380,8 @@ snapshots: dependencies: hermes-estree: 0.25.1 + hexoid@2.0.0: {} + highlight.js@10.7.3: {} highlightjs-vue@1.0.0: {} @@ -22384,7 +22420,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 lowercase-keys@3.0.0: {} @@ -23344,13 +23380,13 @@ snapshots: netmask@2.0.2: {} - next-auth@4.24.10(next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-auth@4.24.10(next@15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 '@panva/hkdf': 1.2.1 cookie: 0.7.2 jose: 4.15.9 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) oauth: 0.9.15 openid-client: 5.7.0 preact: 10.24.3 @@ -23359,12 +23395,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) uuid: 8.3.2 - next-pwa@5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.95.0): + next-pwa@5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.95.0): dependencies: babel-loader: 8.4.1(@babel/core@7.26.0)(webpack@5.95.0) clean-webpack-plugin: 4.0.0(webpack@5.95.0) globby: 11.1.0 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) terser-webpack-plugin: 5.3.10(webpack@5.95.0) workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.95.0) workbox-window: 6.6.0 @@ -23382,11 +23418,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.1.0(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 15.0.3 + '@next/env': 15.1.0 '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.13 + '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001679 postcss: 8.4.31 @@ -23394,14 +23430,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.3 - '@next/swc-darwin-x64': 15.0.3 - '@next/swc-linux-arm64-gnu': 15.0.3 - '@next/swc-linux-arm64-musl': 15.0.3 - '@next/swc-linux-x64-gnu': 15.0.3 - '@next/swc-linux-x64-musl': 15.0.3 - '@next/swc-win32-arm64-msvc': 15.0.3 - '@next/swc-win32-x64-msvc': 15.0.3 + '@next/swc-darwin-arm64': 15.1.0 + '@next/swc-darwin-x64': 15.1.0 + '@next/swc-linux-arm64-gnu': 15.1.0 + '@next/swc-linux-arm64-musl': 15.1.0 + '@next/swc-linux-x64-gnu': 15.1.0 + '@next/swc-linux-x64-musl': 15.1.0 + '@next/swc-win32-arm64-msvc': 15.1.0 + '@next/swc-win32-x64-msvc': 15.1.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -23416,7 +23452,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.8.1 node-abi@3.68.0: dependencies: @@ -23867,7 +23903,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.8.1 password-prompt@1.1.3: dependencies: