From c4d2825c81f81ad8835629fbf6ea8c7383f8c31b Mon Sep 17 00:00:00 2001
From: Igor Monadical
Date: Fri, 5 Sep 2025 18:01:31 -0400
Subject: [PATCH] feat: frontend openapi react query (#606)
* refactor: migrate from @hey-api/openapi-ts to openapi-react-query
- Replace @hey-api/openapi-ts with openapi-typescript and openapi-react-query
- Generate TypeScript types from OpenAPI spec
- Set up React Query infrastructure with QueryClientProvider
- Migrate all API hooks to use React Query patterns
- Maintain backward compatibility for existing components
- Remove old API infrastructure and dependencies
* fix: resolve import errors and add missing api hooks
- Create constants.ts for RECORD_A_MEETING_URL
- Add api-types.ts for backward compatible type exports
- Update all imports from deleted api folder to new locations
- Add missing React Query hooks for rooms and zulip operations
- Create useApi compatibility layer for unmigrated components
* feat: migrate components to React Query hooks
- Add comprehensive API hooks for all operations
- Migrate rooms page to use React Query mutations
- Update transcript title component to use mutation hook
- Refactor share/privacy component with proper error handling
- Remove direct API client usage in favor of hooks
* feat: complete migration from @hey-api/openapi-ts to openapi-react-query
- Migrated all components from useApi compatibility layer to direct React Query hooks
- Added new hooks for participant operations, room meetings, and speaker operations
- Updated all imports from old api module to api-types
- Fixed TypeScript types and API endpoint signatures
- Removed deprecated useApi.ts compatibility layer
- Fixed SourceKind enum values to match OpenAPI spec
- Added @ts-ignore for Zulip endpoints not in OpenAPI spec yet
- Fixed all compilation errors and type issues
* fix: authentication flow with React Query migration
- Fix middleware management in apiClient to properly handle auth tokens
- Update ApiAuthProvider to correctly configure base URL and auth
- Add missing NextAuth API route handler at app/api/auth/[...nextauth]/route.ts
- Remove middleware ejection attempts (not supported by openapi-fetch)
- Use global variables to store current auth token and API URL
- Setup middleware once on initialization instead of repeatedly adding
This fixes the login/logout flow that was broken after migrating from
the useApi compatibility layer to native React Query hooks.
* fix: prevent unauthorized API calls before authentication
- Add global AuthGuard component to handle authentication at layout level
- Make all API query hooks conditional on authentication status
- Define public routes (like /transcripts/new) that don't require auth
- Fix login flow to use NextAuth signIn instead of non-existent /login route
- Prevent 401 errors by waiting for auth token before making API calls
Previously, all routes under (app) were publicly accessible with each page
handling auth individually. Now authentication is enforced globally while
still allowing specific routes to remain public.
* refactor: remove redundant client-side AuthGuard
The authentication is already properly handled by Next.js middleware
in middleware.ts with LOGIN_REQUIRED_PAGES. The middleware approach is
superior as it:
- Provides server-side protection before page loads
- Prevents flash of unauthorized content
- Centralizes auth logic in one place
- Better performance (no client-side JS needed)
Keep the API hooks conditional to prevent 401 errors before token is ready.
* fix: use direct status check for API query authentication
Changed all query hooks to use direct `status === "authenticated"` check
instead of derived `isAuthenticated && !isLoading` to avoid race conditions
where queries might fire before the authentication token is properly set.
This prevents the brief 401 errors that occur on page refresh when the
session is being restored.
* fix: correct content-type header for FormData uploads
Previously, the API client was setting a default Content-Type of application/json
for all requests, which broke file uploads that need multipart/form-data.
Now the client only sets application/json when the body is not FormData,
allowing FormData to automatically set the correct multipart boundary.
* fix: resolve authentication race condition with React Query
Previously, API calls were being made before the auth token was configured,
causing initial 401 errors that would retry with 200 after token setup.
Changes:
- Add global auth readiness tracking in apiClient
- Create useAuthReady hook that checks both session and token state
- Update all API hooks to use isAuthReady instead of just session status
- Add AuthWrapper component at layout level for consistent loading UX
- Show spinner while authentication initializes across all pages
This ensures API calls only fire after authentication is fully configured,
eliminating the 401/retry pattern and improving user experience.
* refactor: clean up api-hooks.ts comments and improve search invalidation
- Remove redundant function category comments (exports are self-explanatory)
- Remove obvious inline comments for query invalidation
- Fix search endpoint invalidation to clear all queries regardless of parameters
* refactor: remove api-types.ts compatibility layer
- Migrated all 29 files from api-types.ts to use reflector-api.d.ts directly
- Removed $SourceKind manual enum in favor of OpenAPI-generated types
- Fixed unrelated Spinner component TypeScript error in AuthWrapper.tsx
- All imports now use: import type { components } from "path/to/reflector-api"
- Deleted api-types.ts file completely
* refactor: rename api-hooks.ts to apiHooks.ts for consistency
- Renamed api-hooks.ts to apiHooks.ts to follow camelCase convention
- Updated all 21 import statements across the codebase
- Maintains consistency with other non-component files (apiClient.tsx, useAuthReady.ts, etc.)
- Follows established naming pattern: PascalCase for components, camelCase for utilities/hooks
* chore: add .playwright-mcp to .gitignore
* refactor: remove SK helper object and use inline type casting in FilterSidebar
Replace the SK (SourceKind) helper object with direct inline type casting
to simplify the code and reduce unnecessary abstraction.
* chore: clean up migration comments from React Query refactoring
- Remove temporary "// Use new React Query hooks" comments
- Remove "// React Query hooks" comments from browse and rooms pages
- Update package.json script name from codegen to openapi for consistency
* refactor: remove Redis dependencies from frontend authentication
- Replace Redis/Redlock with in-memory cache for token management
- Remove @vercel/kv, ioredis, and redlock dependencies from package.json
- Implement simple lock mechanism for concurrent token refresh prevention
- Use Map-based cache with TTL for token storage
- Maintain same authentication flow without external dependencies
This simplifies the infrastructure requirements and removes the need for
Redis while maintaining the same functionality through in-memory caching.
* fix: add staleTime to prevent cross-tab staled data
* fix: remove infinite re-render loop in useSessionAccessToken
The hook was maintaining redundant local state that caused re-renders
on every update, which triggered NextAuth to continuously refetch the
session, resulting in hundreds of POST requests to /api/auth/session.
Simplified the hook to directly return session values without
unnecessary state duplication.
* fix: handle undefined access tokens in auth.ts
Added fallback to empty string for potentially undefined access_token
and refresh_token from NextAuth account object to satisfy
JWTWithAccessToken type requirements.
* Igor/mathieu/frontend openapi react query (#597)
* small typing
* typing fixes
---------
Co-authored-by: Igor Loskutov
* self-review-fix
* authReady callback simplify
* fix auth
* fix compose
* room detail page fix
* compile fix
* room edit fix
* normalize auth provider
* room edition state granular management
* cover TODOs + cross-tab cache
* session auto refresh blink
* schema generator error type doc
* protect from zombie auth
* clarify access token refresh logic a bit
* remove react-query tab sharing cache
* remove react-query tab sharing cache
* websocket dupe react devmode protection
* invalidate room on room update
* redis cache
* test ts server
* ci randomness
* less edgy config (ci)
* less edgy config (ci)
* less edgy config (ci)
* ci randomness
* ci randomness
* ci randomness
* ci randomness
* less edgy config (ci)
* added vs edited room state cleanup
* file upload real-time state management fix
* prettier auth state ternary
* prettier auth state ternary
* proper api address from env
* INTERVAL_REFRESH_MS
* node version 20 for tests
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* github debug
* CI debug
* CI debug
* nextjs magic
* nextjs magic
* doc
* client-side stale auth soft safety net
---------
Co-authored-by: Mathieu Virbel
Co-authored-by: Igor Loskutov
---
.github/workflows/test_next_server.yml | 45 +
.gitignore | 1 +
compose.yml | 3 +
server/reflector/views/rooms.py | 1 +
server/reflector/views/transcripts.py | 3 +-
server/runserver.sh | 2 +-
www/app/(app)/AuthWrapper.tsx | 30 +
.../browse/_components/FilterSidebar.tsx | 7 +-
.../browse/_components/TranscriptCards.tsx | 7 +-
www/app/(app)/browse/page.tsx | 149 +-
www/app/(app)/layout.tsx | 5 +-
www/app/(app)/rooms/_components/RoomCards.tsx | 6 +-
www/app/(app)/rooms/_components/RoomList.tsx | 6 +-
www/app/(app)/rooms/_components/RoomTable.tsx | 6 +-
www/app/(app)/rooms/page.tsx | 233 +-
www/app/(app)/rooms/useRoomList.tsx | 49 +-
.../[transcriptId]/correct/page.tsx | 28 +-
.../correct/participantList.tsx | 197 +-
.../[transcriptId]/correct/topicHeader.tsx | 3 +-
.../[transcriptId]/finalSummary.tsx | 37 +-
.../(app)/transcripts/[transcriptId]/page.tsx | 4 +-
.../[transcriptId]/upload/page.tsx | 13 +-
www/app/(app)/transcripts/createTranscript.ts | 46 +-
.../(app)/transcripts/fileUploadButton.tsx | 64 +-
www/app/(app)/transcripts/new/page.tsx | 39 +-
www/app/(app)/transcripts/player.tsx | 10 +-
www/app/(app)/transcripts/recorder.tsx | 1 -
www/app/(app)/transcripts/shareAndPrivacy.tsx | 47 +-
www/app/(app)/transcripts/shareCopy.tsx | 4 +-
www/app/(app)/transcripts/shareZulip.tsx | 145 +-
www/app/(app)/transcripts/transcriptTitle.tsx | 21 +-
www/app/(app)/transcripts/useMp3.ts | 108 +-
www/app/(app)/transcripts/useParticipants.ts | 74 +-
.../(app)/transcripts/useSearchTranscripts.ts | 123 -
.../(app)/transcripts/useTopicWithWords.ts | 78 +-
www/app/(app)/transcripts/useTopics.ts | 42 +-
www/app/(app)/transcripts/useTranscript.ts | 75 +-
www/app/(app)/transcripts/useWaveform.ts | 47 +-
www/app/(app)/transcripts/useWebRTC.ts | 44 +-
www/app/(app)/transcripts/useWebSockets.ts | 62 +-
www/app/(app)/transcripts/webSocketTypes.ts | 4 +-
www/app/(auth)/userInfo.tsx | 17 +-
www/app/[roomName]/page.tsx | 42 +-
www/app/[roomName]/useRoomMeeting.tsx | 43 +-
www/app/api/OpenApi.ts | 37 -
www/app/api/auth/[...nextauth]/route.ts | 5 +-
www/app/api/core/ApiError.ts | 25 -
www/app/api/core/ApiRequestOptions.ts | 21 -
www/app/api/core/ApiResult.ts | 7 -
www/app/api/core/AxiosHttpRequest.ts | 23 -
www/app/api/core/BaseHttpRequest.ts | 11 -
www/app/api/core/CancelablePromise.ts | 126 -
www/app/api/core/OpenAPI.ts | 57 -
www/app/api/core/request.ts | 387 ---
www/app/api/index.ts | 9 -
www/app/api/schemas.gen.ts | 1776 ----------
www/app/api/services.gen.ts | 942 ------
www/app/api/types.gen.ts | 1143 -------
www/app/api/urls.ts | 1 -
www/app/layout.tsx | 23 +-
www/app/lib/AuthProvider.tsx | 104 +
www/app/lib/SessionAutoRefresh.tsx | 38 +-
www/app/lib/SessionProvider.tsx | 11 -
www/app/lib/__tests__/redisTokenCache.test.ts | 85 +
www/app/lib/apiClient.tsx | 50 +
www/app/lib/apiHooks.ts | 618 ++++
www/app/lib/auth.ts | 166 +-
www/app/lib/authBackend.ts | 178 +
www/app/lib/edgeConfig.ts | 14 +-
www/app/lib/next.ts | 2 +
www/app/lib/queryClient.tsx | 17 +
www/app/lib/redisClient.ts | 46 +
www/app/lib/redisTokenCache.ts | 61 +
www/app/lib/types.ts | 68 +-
www/app/lib/useApi.ts | 37 -
www/app/lib/useLoginRequiredPages.ts | 26 +
www/app/lib/useSessionAccessToken.ts | 42 -
www/app/lib/useSessionStatus.ts | 22 -
www/app/lib/useSessionUser.ts | 33 -
www/app/lib/useUserName.ts | 7 +
www/app/lib/utils.ts | 23 +-
www/app/providers.tsx | 22 +-
www/app/reflector-api.d.ts | 2330 +++++++++++++
www/jest.config.js | 8 +
www/middleware.ts | 11 +-
www/next.config.js | 3 +
www/openapi-ts.config.ts | 14 -
www/package.json | 19 +-
www/pnpm-lock.yaml | 2900 +++++++++++++++--
www/public/service-worker.js | 2 +-
90 files changed, 7253 insertions(+), 6268 deletions(-)
create mode 100644 .github/workflows/test_next_server.yml
create mode 100644 www/app/(app)/AuthWrapper.tsx
delete mode 100644 www/app/(app)/transcripts/useSearchTranscripts.ts
delete mode 100644 www/app/api/OpenApi.ts
delete mode 100644 www/app/api/core/ApiError.ts
delete mode 100644 www/app/api/core/ApiRequestOptions.ts
delete mode 100644 www/app/api/core/ApiResult.ts
delete mode 100644 www/app/api/core/AxiosHttpRequest.ts
delete mode 100644 www/app/api/core/BaseHttpRequest.ts
delete mode 100644 www/app/api/core/CancelablePromise.ts
delete mode 100644 www/app/api/core/OpenAPI.ts
delete mode 100644 www/app/api/core/request.ts
delete mode 100644 www/app/api/index.ts
create mode 100644 www/app/lib/AuthProvider.tsx
delete mode 100644 www/app/lib/SessionProvider.tsx
create mode 100644 www/app/lib/__tests__/redisTokenCache.test.ts
create mode 100644 www/app/lib/apiClient.tsx
create mode 100644 www/app/lib/apiHooks.ts
create mode 100644 www/app/lib/authBackend.ts
create mode 100644 www/app/lib/next.ts
create mode 100644 www/app/lib/queryClient.tsx
create mode 100644 www/app/lib/redisClient.ts
create mode 100644 www/app/lib/redisTokenCache.ts
delete mode 100644 www/app/lib/useApi.ts
create mode 100644 www/app/lib/useLoginRequiredPages.ts
delete mode 100644 www/app/lib/useSessionAccessToken.ts
delete mode 100644 www/app/lib/useSessionStatus.ts
delete mode 100644 www/app/lib/useSessionUser.ts
create mode 100644 www/app/lib/useUserName.ts
create mode 100644 www/app/reflector-api.d.ts
create mode 100644 www/jest.config.js
delete mode 100644 www/openapi-ts.config.ts
diff --git a/.github/workflows/test_next_server.yml b/.github/workflows/test_next_server.yml
new file mode 100644
index 00000000..892566d6
--- /dev/null
+++ b/.github/workflows/test_next_server.yml
@@ -0,0 +1,45 @@
+name: Test Next Server
+
+on:
+ pull_request:
+ paths:
+ - "www/**"
+ push:
+ branches:
+ - main
+ paths:
+ - "www/**"
+
+jobs:
+ test-next-server:
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ working-directory: ./www
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 8
+
+ - name: Setup Node.js cache
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'pnpm'
+ cache-dependency-path: './www/pnpm-lock.yaml'
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Run tests
+ run: pnpm test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 29d56f25..f3249991 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ server/test.sqlite
CLAUDE.local.md
www/.env.development
www/.env.production
+.playwright-mcp
diff --git a/compose.yml b/compose.yml
index 492c7b8c..acbfd3b5 100644
--- a/compose.yml
+++ b/compose.yml
@@ -6,6 +6,7 @@ services:
- 1250:1250
volumes:
- ./server/:/app/
+ - /app/.venv
env_file:
- ./server/.env
environment:
@@ -16,6 +17,7 @@ services:
context: server
volumes:
- ./server/:/app/
+ - /app/.venv
env_file:
- ./server/.env
environment:
@@ -26,6 +28,7 @@ services:
context: server
volumes:
- ./server/:/app/
+ - /app/.venv
env_file:
- ./server/.env
environment:
diff --git a/server/reflector/views/rooms.py b/server/reflector/views/rooms.py
index 40e81aeb..cc00f3c0 100644
--- a/server/reflector/views/rooms.py
+++ b/server/reflector/views/rooms.py
@@ -197,6 +197,7 @@ async def rooms_create_meeting(
end_date = current_time + timedelta(hours=8)
whereby_meeting = await create_meeting("", end_date=end_date, room=room)
+
await upload_logo(whereby_meeting["roomName"], "./images/logo.png")
# Now try to save to database
diff --git a/server/reflector/views/transcripts.py b/server/reflector/views/transcripts.py
index 3f32a9bd..9acfcbf8 100644
--- a/server/reflector/views/transcripts.py
+++ b/server/reflector/views/transcripts.py
@@ -27,6 +27,7 @@ from reflector.db.search import (
from reflector.db.transcripts import (
SourceKind,
TranscriptParticipant,
+ TranscriptStatus,
TranscriptTopic,
transcripts_controller,
)
@@ -63,7 +64,7 @@ class GetTranscriptMinimal(BaseModel):
id: str
user_id: str | None
name: str
- status: str
+ status: TranscriptStatus
locked: bool
duration: float
title: str | None
diff --git a/server/runserver.sh b/server/runserver.sh
index a4fb6869..9cccaacb 100755
--- a/server/runserver.sh
+++ b/server/runserver.sh
@@ -2,7 +2,7 @@
if [ "${ENTRYPOINT}" = "server" ]; then
uv run alembic upgrade head
- uv run -m reflector.app
+ uv run uvicorn reflector.app:app --host 0.0.0.0 --port 1250
elif [ "${ENTRYPOINT}" = "worker" ]; then
uv run celery -A reflector.worker.app worker --loglevel=info
elif [ "${ENTRYPOINT}" = "beat" ]; then
diff --git a/www/app/(app)/AuthWrapper.tsx b/www/app/(app)/AuthWrapper.tsx
new file mode 100644
index 00000000..57038b7b
--- /dev/null
+++ b/www/app/(app)/AuthWrapper.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { Flex, Spinner } from "@chakra-ui/react";
+import { useAuth } from "../lib/AuthProvider";
+import { useLoginRequiredPages } from "../lib/useLoginRequiredPages";
+
+export default function AuthWrapper({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const auth = useAuth();
+ const redirectPath = useLoginRequiredPages();
+ const redirectHappens = !!redirectPath;
+
+ if (auth.status === "loading" || redirectHappens) {
+ return (
+
+
+
+ );
+ }
+
+ return <>{children}>;
+}
diff --git a/www/app/(app)/browse/_components/FilterSidebar.tsx b/www/app/(app)/browse/_components/FilterSidebar.tsx
index b2abe481..6eef61b8 100644
--- a/www/app/(app)/browse/_components/FilterSidebar.tsx
+++ b/www/app/(app)/browse/_components/FilterSidebar.tsx
@@ -1,7 +1,10 @@
import React from "react";
import { Box, Stack, Link, Heading } from "@chakra-ui/react";
import NextLink from "next/link";
-import { Room, SourceKind } from "../../../api";
+import type { components } from "../../../reflector-api";
+
+type Room = components["schemas"]["Room"];
+type SourceKind = components["schemas"]["SourceKind"];
interface FilterSidebarProps {
rooms: Room[];
@@ -72,7 +75,7 @@ export default function FilterSidebar({
key={room.id}
as={NextLink}
href="#"
- onClick={() => onFilterChange("room", room.id)}
+ onClick={() => onFilterChange("room" as SourceKind, room.id)}
color={
selectedSourceKind === "room" && selectedRoomId === room.id
? "blue.500"
diff --git a/www/app/(app)/browse/_components/TranscriptCards.tsx b/www/app/(app)/browse/_components/TranscriptCards.tsx
index b67e71e7..8dbc3568 100644
--- a/www/app/(app)/browse/_components/TranscriptCards.tsx
+++ b/www/app/(app)/browse/_components/TranscriptCards.tsx
@@ -18,7 +18,10 @@ import {
highlightMatches,
generateTextFragment,
} from "../../../lib/textHighlight";
-import { SearchResult } from "../../../api";
+import type { components } from "../../../reflector-api";
+
+type SearchResult = components["schemas"]["SearchResult"];
+type SourceKind = components["schemas"]["SourceKind"];
interface TranscriptCardsProps {
results: SearchResult[];
@@ -120,7 +123,7 @@ function TranscriptCard({
: "N/A";
const formattedDate = formatLocalDate(result.created_at);
const source =
- result.source_kind === "room"
+ result.source_kind === ("room" as SourceKind)
? result.room_name || result.room_id
: result.source_kind;
diff --git a/www/app/(app)/browse/page.tsx b/www/app/(app)/browse/page.tsx
index e7522e14..8523650e 100644
--- a/www/app/(app)/browse/page.tsx
+++ b/www/app/(app)/browse/page.tsx
@@ -19,37 +19,33 @@ import {
parseAsStringLiteral,
} from "nuqs";
import { LuX } from "react-icons/lu";
-import { useSearchTranscripts } from "../transcripts/useSearchTranscripts";
-import useSessionUser from "../../lib/useSessionUser";
-import { Room, SourceKind, SearchResult, $SourceKind } from "../../api";
-import useApi from "../../lib/useApi";
-import { useError } from "../../(errors)/errorContext";
+import type { components } from "../../reflector-api";
+
+type Room = components["schemas"]["Room"];
+type SourceKind = components["schemas"]["SourceKind"];
+type SearchResult = components["schemas"]["SearchResult"];
+import {
+ useRoomsList,
+ useTranscriptsSearch,
+ useTranscriptDelete,
+ useTranscriptProcess,
+} from "../../lib/apiHooks";
import FilterSidebar from "./_components/FilterSidebar";
import Pagination, {
FIRST_PAGE,
PaginationPage,
parsePaginationPage,
totalPages as getTotalPages,
+ paginationPageTo0Based,
} from "./_components/Pagination";
import TranscriptCards from "./_components/TranscriptCards";
import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog";
import { formatLocalDate } from "../../lib/time";
import { RECORD_A_MEETING_URL } from "../../api/urls";
+import { useUserName } from "../../lib/useUserName";
const SEARCH_FORM_QUERY_INPUT_NAME = "query" as const;
-const usePrefetchRooms = (setRooms: (rooms: Room[]) => void): void => {
- const { setError } = useError();
- const api = useApi();
- useEffect(() => {
- if (!api) return;
- api
- .v1RoomsList({ page: 1 })
- .then((rooms) => setRooms(rooms.items))
- .catch((err) => setError(err, "There was an error fetching the rooms"));
- }, [api, setError]);
-};
-
const SearchForm: React.FC<{
setPage: (page: PaginationPage) => void;
sourceKind: SourceKind | null;
@@ -69,7 +65,6 @@ const SearchForm: React.FC<{
searchQuery,
setSearchQuery,
}) => {
- // to keep the search input controllable + more fine grained control (urlSearchQuery is updated on submits)
const [searchInputValue, setSearchInputValue] = useState(searchQuery || "");
const handleSearchQuerySubmit = async (d: FormData) => {
await setSearchQuery((d.get(SEARCH_FORM_QUERY_INPUT_NAME) as string) || "");
@@ -163,7 +158,6 @@ const UnderSearchFormFilterIndicators: React.FC<{
p="1px"
onClick={() => {
setSourceKind(null);
- // TODO questionable
setRoomId(null);
}}
_hover={{ bg: "blue.200" }}
@@ -209,7 +203,11 @@ export default function TranscriptBrowser() {
const [urlSourceKind, setUrlSourceKind] = useQueryState(
"source",
- parseAsStringLiteral($SourceKind.enum).withOptions({
+ parseAsStringLiteral([
+ "room",
+ "live",
+ "file",
+ ] as const satisfies SourceKind[]).withOptions({
shallow: false,
}),
);
@@ -229,46 +227,40 @@ export default function TranscriptBrowser() {
useEffect(() => {
const maybePage = parsePaginationPage(urlPage);
if ("error" in maybePage) {
- setPage(FIRST_PAGE).then(() => {
- /*may be called n times we dont care*/
- });
+ setPage(FIRST_PAGE).then(() => {});
return;
}
_setSafePage(maybePage.value);
}, [urlPage]);
- const [rooms, setRooms] = useState([]);
-
const pageSize = 20;
+
const {
- results,
- totalCount: totalResults,
- isLoading,
- reload,
- } = useSearchTranscripts(
- urlSearchQuery,
- {
- roomIds: urlRoomId ? [urlRoomId] : null,
- sourceKind: urlSourceKind,
- },
- {
- pageSize,
- page,
- },
- );
+ data: searchData,
+ isLoading: searchLoading,
+ refetch: reloadSearch,
+ } = useTranscriptsSearch(urlSearchQuery, {
+ limit: pageSize,
+ offset: paginationPageTo0Based(page) * pageSize,
+ room_id: urlRoomId || undefined,
+ source_kind: urlSourceKind || undefined,
+ });
+
+ const results = searchData?.results || [];
+ const totalResults = searchData?.total || 0;
+
+ // Fetch rooms
+ const { data: roomsData } = useRoomsList(1);
+ const rooms = roomsData?.items || [];
const totalPages = getTotalPages(totalResults, pageSize);
- const userName = useSessionUser().name;
+ const userName = useUserName();
const [deletionLoading, setDeletionLoading] = useState(false);
- const api = useApi();
- const { setError } = useError();
const cancelRef = React.useRef(null);
const [transcriptToDeleteId, setTranscriptToDeleteId] =
React.useState();
- usePrefetchRooms(setRooms);
-
const handleFilterTranscripts = (
sourceKind: SourceKind | null,
roomId: string,
@@ -280,44 +272,37 @@ export default function TranscriptBrowser() {
const onCloseDeletion = () => setTranscriptToDeleteId(undefined);
+ const deleteTranscript = useTranscriptDelete();
+ const processTranscript = useTranscriptProcess();
+
const confirmDeleteTranscript = (transcriptId: string) => {
- if (!api || deletionLoading) return;
+ if (deletionLoading) return;
setDeletionLoading(true);
- api
- .v1TranscriptDelete({ transcriptId })
- .then(() => {
- setDeletionLoading(false);
- onCloseDeletion();
- reload();
- })
- .catch((err) => {
- setDeletionLoading(false);
- setError(err, "There was an error deleting the transcript");
- });
+ deleteTranscript.mutate(
+ {
+ params: {
+ path: { transcript_id: transcriptId },
+ },
+ },
+ {
+ onSuccess: () => {
+ setDeletionLoading(false);
+ onCloseDeletion();
+ reloadSearch();
+ },
+ onError: () => {
+ setDeletionLoading(false);
+ },
+ },
+ );
};
const handleProcessTranscript = (transcriptId: string) => {
- if (!api) {
- console.error("API not available on handleProcessTranscript");
- return;
- }
- api
- .v1TranscriptProcess({ transcriptId })
- .then((result) => {
- const status =
- result && typeof result === "object" && "status" in result
- ? (result as { status: string }).status
- : undefined;
- if (status === "already running") {
- setError(
- new Error("Processing is already running, please wait"),
- "Processing is already running, please wait",
- );
- }
- })
- .catch((err) => {
- setError(err, "There was an error processing the transcript");
- });
+ processTranscript.mutate({
+ params: {
+ path: { transcript_id: transcriptId },
+ },
+ });
};
const transcriptToDelete = results?.find(
@@ -332,7 +317,7 @@ export default function TranscriptBrowser() {
? transcriptToDelete.room_name || transcriptToDelete.room_id
: transcriptToDelete?.source_kind;
- if (isLoading && results.length === 0) {
+ if (searchLoading && results.length === 0) {
return (
{userName ? `${userName}'s Transcriptions` : "Your Transcriptions"}{" "}
- {(isLoading || deletionLoading) && }
+ {(searchLoading || deletionLoading) && }
@@ -403,12 +388,12 @@ export default function TranscriptBrowser() {
- {!isLoading && results.length === 0 && (
+ {!searchLoading && results.length === 0 && (
)}
diff --git a/www/app/(app)/layout.tsx b/www/app/(app)/layout.tsx
index 5760e19d..801be28f 100644
--- a/www/app/(app)/layout.tsx
+++ b/www/app/(app)/layout.tsx
@@ -2,9 +2,8 @@ import { Container, Flex, Link } from "@chakra-ui/react";
import { getConfig } from "../lib/edgeConfig";
import NextLink from "next/link";
import Image from "next/image";
-import About from "../(aboutAndPrivacy)/about";
-import Privacy from "../(aboutAndPrivacy)/privacy";
import UserInfo from "../(auth)/userInfo";
+import AuthWrapper from "./AuthWrapper";
import { RECORD_A_MEETING_URL } from "../api/urls";
export default async function AppLayout({
@@ -90,7 +89,7 @@ export default async function AppLayout({
- {children}
+ {children}
);
}
diff --git a/www/app/(app)/rooms/_components/RoomCards.tsx b/www/app/(app)/rooms/_components/RoomCards.tsx
index 16748d90..8b22ad72 100644
--- a/www/app/(app)/rooms/_components/RoomCards.tsx
+++ b/www/app/(app)/rooms/_components/RoomCards.tsx
@@ -12,11 +12,13 @@ import {
HStack,
} from "@chakra-ui/react";
import { LuLink } from "react-icons/lu";
-import { RoomDetails } from "../../../api";
+import type { components } from "../../../reflector-api";
+
+type Room = components["schemas"]["Room"];
import { RoomActionsMenu } from "./RoomActionsMenu";
interface RoomCardsProps {
- rooms: RoomDetails[];
+ rooms: Room[];
linkCopied: string;
onCopyUrl: (roomName: string) => void;
onEdit: (roomId: string, roomData: any) => void;
diff --git a/www/app/(app)/rooms/_components/RoomList.tsx b/www/app/(app)/rooms/_components/RoomList.tsx
index 73fe8a5c..218c890c 100644
--- a/www/app/(app)/rooms/_components/RoomList.tsx
+++ b/www/app/(app)/rooms/_components/RoomList.tsx
@@ -1,11 +1,13 @@
import { Box, Heading, Text, VStack } from "@chakra-ui/react";
-import { RoomDetails } from "../../../api";
+import type { components } from "../../../reflector-api";
+
+type Room = components["schemas"]["Room"];
import { RoomTable } from "./RoomTable";
import { RoomCards } from "./RoomCards";
interface RoomListProps {
title: string;
- rooms: RoomDetails[];
+ rooms: Room[];
linkCopied: string;
onCopyUrl: (roomName: string) => void;
onEdit: (roomId: string, roomData: any) => void;
diff --git a/www/app/(app)/rooms/_components/RoomTable.tsx b/www/app/(app)/rooms/_components/RoomTable.tsx
index 93d05b61..113eca7f 100644
--- a/www/app/(app)/rooms/_components/RoomTable.tsx
+++ b/www/app/(app)/rooms/_components/RoomTable.tsx
@@ -9,11 +9,13 @@ import {
Spinner,
} from "@chakra-ui/react";
import { LuLink } from "react-icons/lu";
-import { RoomDetails } from "../../../api";
+import type { components } from "../../../reflector-api";
+
+type Room = components["schemas"]["Room"];
import { RoomActionsMenu } from "./RoomActionsMenu";
interface RoomTableProps {
- rooms: RoomDetails[];
+ rooms: Room[];
linkCopied: string;
onCopyUrl: (roomName: string) => void;
onEdit: (roomId: string, roomData: any) => void;
diff --git a/www/app/(app)/rooms/page.tsx b/www/app/(app)/rooms/page.tsx
index 33cfa6b3..8b1378df 100644
--- a/www/app/(app)/rooms/page.tsx
+++ b/www/app/(app)/rooms/page.tsx
@@ -15,13 +15,24 @@ import {
createListCollection,
useDisclosure,
} from "@chakra-ui/react";
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import { LuEye, LuEyeOff } from "react-icons/lu";
-import useApi from "../../lib/useApi";
import useRoomList from "./useRoomList";
-import { ApiError, RoomDetails } from "../../api";
+import type { components } from "../../reflector-api";
+import {
+ useRoomCreate,
+ useRoomUpdate,
+ useRoomDelete,
+ useZulipStreams,
+ useZulipTopics,
+ useRoomGet,
+ useRoomTestWebhook,
+} from "../../lib/apiHooks";
import { RoomList } from "./_components/RoomList";
import { PaginationPage } from "../browse/_components/Pagination";
+import { assertExists } from "../../lib/utils";
+
+type Room = components["schemas"]["Room"];
interface SelectOption {
label: string;
@@ -76,66 +87,77 @@ export default function RoomsList() {
const recordingTypeCollection = createListCollection({
items: recordingTypeOptions,
});
- const [room, setRoom] = useState(roomInitialState);
+ const [roomInput, setRoomInput] = useState(
+ null,
+ );
const [isEditing, setIsEditing] = useState(false);
- const [editRoomId, setEditRoomId] = useState("");
- const api = useApi();
- // TODO seems to be no setPage calls
- const [page, setPage] = useState(1);
- const { loading, response, refetch } = useRoomList(PaginationPage(page));
- const [streams, setStreams] = useState([]);
- const [topics, setTopics] = useState([]);
+ const [editRoomId, setEditRoomId] = useState(null);
+ const {
+ loading,
+ response,
+ refetch,
+ error: roomListError,
+ } = useRoomList(PaginationPage(1));
const [nameError, setNameError] = useState("");
const [linkCopied, setLinkCopied] = useState("");
+ const [selectedStreamId, setSelectedStreamId] = useState(null);
const [testingWebhook, setTestingWebhook] = useState(false);
const [webhookTestResult, setWebhookTestResult] = useState(
null,
);
const [showWebhookSecret, setShowWebhookSecret] = useState(false);
- interface Stream {
- stream_id: number;
- name: string;
- }
- interface Topic {
- name: string;
- }
+ const createRoomMutation = useRoomCreate();
+ const updateRoomMutation = useRoomUpdate();
+ const deleteRoomMutation = useRoomDelete();
+ const { data: streams = [] } = useZulipStreams();
+ const { data: topics = [] } = useZulipTopics(selectedStreamId);
+ const {
+ data: detailedEditedRoom,
+ isLoading: isDetailedEditedRoomLoading,
+ error: detailedEditedRoomError,
+ } = useRoomGet(editRoomId);
+
+ const error = roomListError || detailedEditedRoomError;
+
+ // room being edited, as fetched from the server
+ const editedRoom: typeof roomInitialState | null = useMemo(
+ () =>
+ detailedEditedRoom
+ ? {
+ name: detailedEditedRoom.name,
+ zulipAutoPost: detailedEditedRoom.zulip_auto_post,
+ zulipStream: detailedEditedRoom.zulip_stream,
+ zulipTopic: detailedEditedRoom.zulip_topic,
+ isLocked: detailedEditedRoom.is_locked,
+ roomMode: detailedEditedRoom.room_mode,
+ recordingType: detailedEditedRoom.recording_type,
+ recordingTrigger: detailedEditedRoom.recording_trigger,
+ isShared: detailedEditedRoom.is_shared,
+ webhookUrl: detailedEditedRoom.webhook_url || "",
+ webhookSecret: detailedEditedRoom.webhook_secret || "",
+ }
+ : null,
+ [detailedEditedRoom],
+ );
+
+ // a room input value or a last api room state
+ const room = roomInput || editedRoom || roomInitialState;
+
+ const roomTestWebhookMutation = useRoomTestWebhook();
+
+ // Update selected stream ID when zulip stream changes
useEffect(() => {
- const fetchZulipStreams = async () => {
- if (!api) return;
-
- try {
- const response = await api.v1ZulipGetStreams();
- setStreams(response);
- } catch (error) {
- console.error("Error fetching Zulip streams:", error);
+ if (room.zulipStream && streams.length > 0) {
+ const selectedStream = streams.find((s) => s.name === room.zulipStream);
+ if (selectedStream !== undefined) {
+ setSelectedStreamId(selectedStream.stream_id);
}
- };
-
- if (room.zulipAutoPost) {
- fetchZulipStreams();
+ } else {
+ setSelectedStreamId(null);
}
- }, [room.zulipAutoPost, !api]);
-
- useEffect(() => {
- const fetchZulipTopics = async () => {
- if (!api || !room.zulipStream) return;
- try {
- const selectedStream = streams.find((s) => s.name === room.zulipStream);
- if (selectedStream) {
- const response = await api.v1ZulipGetTopics({
- streamId: selectedStream.stream_id,
- });
- setTopics(response);
- }
- } catch (error) {
- console.error("Error fetching Zulip topics:", error);
- }
- };
-
- fetchZulipTopics();
- }, [room.zulipStream, streams, api]);
+ }, [room.zulipStream, streams]);
const streamOptions: SelectOption[] = streams.map((stream) => {
return { label: stream.name, value: stream.name };
@@ -167,35 +189,42 @@ export default function RoomsList() {
const handleCloseDialog = () => {
setShowWebhookSecret(false);
setWebhookTestResult(null);
+ setEditRoomId(null);
onClose();
};
const handleTestWebhook = async () => {
- if (!room.webhookUrl || !editRoomId) {
+ if (!room.webhookUrl) {
setWebhookTestResult("Please enter a webhook URL first");
return;
}
+ if (!editRoomId) {
+ console.error("No room ID to test webhook");
+ return;
+ }
setTestingWebhook(true);
setWebhookTestResult(null);
try {
- const response = await api?.v1RoomsTestWebhook({
- roomId: editRoomId,
+ const response = await roomTestWebhookMutation.mutateAsync({
+ params: {
+ path: {
+ room_id: editRoomId,
+ },
+ },
});
- if (response?.success) {
+ if (response.success) {
setWebhookTestResult(
`✅ Webhook test successful! Status: ${response.status_code}`,
);
} else {
let errorMsg = `❌ Webhook test failed`;
- if (response?.status_code) {
- errorMsg += ` (Status: ${response.status_code})`;
- }
- if (response?.error) {
+ errorMsg += ` (Status: ${response.status_code})`;
+ if (response.error) {
errorMsg += `: ${response.error}`;
- } else if (response?.response_preview) {
+ } else if (response.response_preview) {
// Try to parse and extract meaningful error from response
// Specific to N8N at the moment, as there is no specification for that
// We could just display as is, but decided here to dig a little bit more.
@@ -249,27 +278,29 @@ export default function RoomsList() {
};
if (isEditing) {
- await api?.v1RoomsUpdate({
- roomId: editRoomId,
- requestBody: roomData,
+ await updateRoomMutation.mutateAsync({
+ params: {
+ path: { room_id: assertExists(editRoomId) },
+ },
+ body: roomData,
});
} else {
- await api?.v1RoomsCreate({
- requestBody: roomData,
+ await createRoomMutation.mutateAsync({
+ body: roomData,
});
}
- setRoom(roomInitialState);
+ setRoomInput(null);
setIsEditing(false);
setEditRoomId("");
setNameError("");
refetch();
+ onClose();
handleCloseDialog();
- } catch (err) {
+ } catch (err: any) {
if (
- err instanceof ApiError &&
- err.status === 400 &&
- (err.body as any).detail == "Room name is not unique"
+ err?.status === 400 &&
+ err?.body?.detail == "Room name is not unique"
) {
setNameError(
"This room name is already taken. Please choose a different name.",
@@ -280,46 +311,11 @@ export default function RoomsList() {
}
};
- const handleEditRoom = async (roomId, roomData) => {
+ const handleEditRoom = async (roomId: string, roomData) => {
// Reset states
setShowWebhookSecret(false);
setWebhookTestResult(null);
- // Fetch full room details to get webhook fields
- try {
- const detailedRoom = await api?.v1RoomsGet({ roomId });
- if (detailedRoom) {
- setRoom({
- name: detailedRoom.name,
- zulipAutoPost: detailedRoom.zulip_auto_post,
- zulipStream: detailedRoom.zulip_stream,
- zulipTopic: detailedRoom.zulip_topic,
- isLocked: detailedRoom.is_locked,
- roomMode: detailedRoom.room_mode,
- recordingType: detailedRoom.recording_type,
- recordingTrigger: detailedRoom.recording_trigger,
- isShared: detailedRoom.is_shared,
- webhookUrl: detailedRoom.webhook_url || "",
- webhookSecret: detailedRoom.webhook_secret || "",
- });
- }
- } catch (error) {
- console.error("Failed to fetch room details, using list data:", error);
- // Fallback to using the data from the list
- setRoom({
- name: roomData.name,
- zulipAutoPost: roomData.zulip_auto_post,
- zulipStream: roomData.zulip_stream,
- zulipTopic: roomData.zulip_topic,
- isLocked: roomData.is_locked,
- roomMode: roomData.room_mode,
- recordingType: roomData.recording_type,
- recordingTrigger: roomData.recording_trigger,
- isShared: roomData.is_shared,
- webhookUrl: roomData.webhook_url || "",
- webhookSecret: roomData.webhook_secret || "",
- });
- }
setEditRoomId(roomId);
setIsEditing(true);
setNameError("");
@@ -328,8 +324,10 @@ export default function RoomsList() {
const handleDeleteRoom = async (roomId: string) => {
try {
- await api?.v1RoomsDelete({
- roomId,
+ await deleteRoomMutation.mutateAsync({
+ params: {
+ path: { room_id: roomId },
+ },
});
refetch();
} catch (err) {
@@ -346,15 +344,15 @@ export default function RoomsList() {
.toLowerCase();
setNameError("");
}
- setRoom({
+ setRoomInput({
...room,
[name]: type === "checkbox" ? checked : value,
});
};
- const myRooms: RoomDetails[] =
+ const myRooms: Room[] =
response?.items.filter((roomData) => !roomData.is_shared) || [];
- const sharedRooms: RoomDetails[] =
+ const sharedRooms: Room[] =
response?.items.filter((roomData) => roomData.is_shared) || [];
if (loading && !response)
@@ -369,6 +367,9 @@ export default function RoomsList() {
);
+ if (roomListError)
+ return {`${roomListError.name}: ${roomListError.message}`}
;
+
return (
{
setIsEditing(false);
- setRoom(roomInitialState);
+ setRoomInput(null);
setNameError("");
setShowWebhookSecret(false);
setWebhookTestResult(null);
@@ -456,7 +457,7 @@ export default function RoomsList() {
- setRoom({ ...room, roomMode: e.value[0] })
+ setRoomInput({ ...room, roomMode: e.value[0] })
}
collection={roomModeCollection}
>
@@ -486,7 +487,7 @@ export default function RoomsList() {
- setRoom({
+ setRoomInput({
...room,
recordingType: e.value[0],
recordingTrigger:
@@ -521,7 +522,7 @@ export default function RoomsList() {
- setRoom({ ...room, recordingTrigger: e.value[0] })
+ setRoomInput({ ...room, recordingTrigger: e.value[0] })
}
collection={recordingTriggerCollection}
disabled={room.recordingType !== "cloud"}
@@ -576,7 +577,7 @@ export default function RoomsList() {
- setRoom({
+ setRoomInput({
...room,
zulipStream: e.value[0],
zulipTopic: "",
@@ -611,7 +612,7 @@ export default function RoomsList() {
- setRoom({ ...room, zulipTopic: e.value[0] })
+ setRoomInput({ ...room, zulipTopic: e.value[0] })
}
collection={topicCollection}
disabled={!room.zulipAutoPost}
diff --git a/www/app/(app)/rooms/useRoomList.tsx b/www/app/(app)/rooms/useRoomList.tsx
index c1021ade..e8d11250 100644
--- a/www/app/(app)/rooms/useRoomList.tsx
+++ b/www/app/(app)/rooms/useRoomList.tsx
@@ -1,48 +1,27 @@
-import { useEffect, useState } from "react";
-import { useError } from "../../(errors)/errorContext";
-import useApi from "../../lib/useApi";
-import { Page_RoomDetails_ } from "../../api";
+import { useRoomsList } from "../../lib/apiHooks";
+import type { components } from "../../reflector-api";
+
+type Page_Room_ = components["schemas"]["Page_RoomDetails_"];
import { PaginationPage } from "../browse/_components/Pagination";
type RoomList = {
- response: Page_RoomDetails_ | null;
+ response: Page_Room_ | null;
loading: boolean;
error: Error | null;
refetch: () => void;
};
-//always protected
+// Wrapper to maintain backward compatibility
const useRoomList = (page: PaginationPage): RoomList => {
- const [response, setResponse] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
- const [refetchCount, setRefetchCount] = useState(0);
-
- const refetch = () => {
- setLoading(true);
- setRefetchCount(refetchCount + 1);
+ const { data, isLoading, error, refetch } = useRoomsList(page);
+ return {
+ response: data || null,
+ loading: isLoading,
+ error: error
+ ? new Error(error.detail ? JSON.stringify(error.detail) : undefined)
+ : null,
+ refetch,
};
-
- useEffect(() => {
- if (!api) return;
- setLoading(true);
- api
- .v1RoomsList({ page })
- .then((response) => {
- setResponse(response);
- setLoading(false);
- })
- .catch((err) => {
- setResponse(null);
- setLoading(false);
- setError(err);
- setErrorState(err);
- });
- }, [!api, page, refetchCount]);
-
- return { response, loading, error, refetch };
};
export default useRoomList;
diff --git a/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx b/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx
index 9eff7b60..c885ca6e 100644
--- a/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx
@@ -6,9 +6,10 @@ import TopicPlayer from "./topicPlayer";
import useParticipants from "../../useParticipants";
import useTopicWithWords from "../../useTopicWithWords";
import ParticipantList from "./participantList";
-import { GetTranscriptTopic } from "../../../../api";
+import type { components } from "../../../../reflector-api";
+type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
import { SelectedText, selectedTextIsTimeSlice } from "./types";
-import useApi from "../../../../lib/useApi";
+import { useTranscriptUpdate } from "../../../../lib/apiHooks";
import useTranscript from "../../useTranscript";
import { useError } from "../../../../(errors)/errorContext";
import { useRouter } from "next/navigation";
@@ -23,7 +24,7 @@ export type TranscriptCorrect = {
export default function TranscriptCorrect({
params: { transcriptId },
}: TranscriptCorrect) {
- const api = useApi();
+ const updateTranscriptMutation = useTranscriptUpdate();
const transcript = useTranscript(transcriptId);
const stateCurrentTopic = useState();
const [currentTopic, _sct] = stateCurrentTopic;
@@ -34,16 +35,21 @@ export default function TranscriptCorrect({
const { setError } = useError();
const router = useRouter();
- const markAsDone = () => {
+ const markAsDone = async () => {
if (transcript.response && !transcript.response.reviewed) {
- api
- ?.v1TranscriptUpdate({ transcriptId, requestBody: { reviewed: true } })
- .then(() => {
- router.push(`/transcripts/${transcriptId}`);
- })
- .catch((e) => {
- setError(e, "Error marking as done");
+ try {
+ await updateTranscriptMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: { reviewed: true },
});
+ router.push(`/transcripts/${transcriptId}`);
+ } catch (e) {
+ setError(e as Error, "Error marking as done");
+ }
}
};
diff --git a/www/app/(app)/transcripts/[transcriptId]/correct/participantList.tsx b/www/app/(app)/transcripts/[transcriptId]/correct/participantList.tsx
index e9297c4b..7c60ea54 100644
--- a/www/app/(app)/transcripts/[transcriptId]/correct/participantList.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/correct/participantList.tsx
@@ -1,8 +1,15 @@
import { faArrowTurnDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ChangeEvent, useEffect, useRef, useState } from "react";
-import { Participant } from "../../../../api";
-import useApi from "../../../../lib/useApi";
+import type { components } from "../../../../reflector-api";
+type Participant = components["schemas"]["Participant"];
+import {
+ useTranscriptSpeakerAssign,
+ useTranscriptSpeakerMerge,
+ useTranscriptParticipantUpdate,
+ useTranscriptParticipantCreate,
+ useTranscriptParticipantDelete,
+} from "../../../../lib/apiHooks";
import { UseParticipants } from "../../useParticipants";
import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./types";
import { useError } from "../../../../(errors)/errorContext";
@@ -30,9 +37,19 @@ const ParticipantList = ({
topicWithWords,
stateSelectedText,
}: ParticipantList) => {
- const api = useApi();
const { setError } = useError();
- const [loading, setLoading] = useState(false);
+ const speakerAssignMutation = useTranscriptSpeakerAssign();
+ const speakerMergeMutation = useTranscriptSpeakerMerge();
+ const participantUpdateMutation = useTranscriptParticipantUpdate();
+ const participantCreateMutation = useTranscriptParticipantCreate();
+ const participantDeleteMutation = useTranscriptParticipantDelete();
+
+ const loading =
+ speakerAssignMutation.isPending ||
+ speakerMergeMutation.isPending ||
+ participantUpdateMutation.isPending ||
+ participantCreateMutation.isPending ||
+ participantDeleteMutation.isPending;
const [participantInput, setParticipantInput] = useState("");
const inputRef = useRef(null);
const [selectedText, setSelectedText] = stateSelectedText;
@@ -103,7 +120,6 @@ const ParticipantList = ({
const onSuccess = () => {
topicWithWords.refetch();
participants.refetch();
- setLoading(false);
setAction(null);
setSelectedText(undefined);
setSelectedParticipant(undefined);
@@ -120,11 +136,14 @@ const ParticipantList = ({
if (loading || participants.loading || topicWithWords.loading) return;
if (!selectedTextIsTimeSlice(selectedText)) return;
- setLoading(true);
try {
- await api?.v1TranscriptAssignSpeaker({
- transcriptId,
- requestBody: {
+ await speakerAssignMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
participant: participant.id,
timestamp_from: selectedText.start,
timestamp_to: selectedText.end,
@@ -132,8 +151,7 @@ const ParticipantList = ({
});
onSuccess();
} catch (error) {
- setError(error, "There was an error assigning");
- setLoading(false);
+ setError(error as Error, "There was an error assigning");
throw error;
}
};
@@ -141,32 +159,38 @@ const ParticipantList = ({
const mergeSpeaker =
(speakerFrom, participantTo: Participant) => async () => {
if (loading || participants.loading || topicWithWords.loading) return;
- setLoading(true);
+
if (participantTo.speaker) {
try {
- await api?.v1TranscriptMergeSpeaker({
- transcriptId,
- requestBody: {
+ await speakerMergeMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
speaker_from: speakerFrom,
speaker_to: participantTo.speaker,
},
});
onSuccess();
} catch (error) {
- setError(error, "There was an error merging");
- setLoading(false);
+ setError(error as Error, "There was an error merging");
}
} else {
try {
- await api?.v1TranscriptUpdateParticipant({
- transcriptId,
- participantId: participantTo.id,
- requestBody: { speaker: speakerFrom },
+ await participantUpdateMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ participant_id: participantTo.id,
+ },
+ },
+ body: { speaker: speakerFrom },
});
onSuccess();
} catch (error) {
- setError(error, "There was an error merging (update)");
- setLoading(false);
+ setError(error as Error, "There was an error merging (update)");
}
}
};
@@ -186,105 +210,106 @@ const ParticipantList = ({
(p) => p.speaker == selectedText,
);
if (participant && participant.name !== participantInput) {
- setLoading(true);
- api
- ?.v1TranscriptUpdateParticipant({
- transcriptId,
- participantId: participant.id,
- requestBody: {
+ try {
+ await participantUpdateMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ participant_id: participant.id,
+ },
+ },
+ body: {
name: participantInput,
},
- })
- .then(() => {
- participants.refetch();
- setLoading(false);
- setAction(null);
- })
- .catch((e) => {
- setError(e, "There was an error renaming");
- setLoading(false);
});
+ participants.refetch();
+ setAction(null);
+ } catch (e) {
+ setError(e as Error, "There was an error renaming");
+ }
}
} else if (
action == "Create to rename" &&
selectedTextIsSpeaker(selectedText)
) {
- setLoading(true);
- api
- ?.v1TranscriptAddParticipant({
- transcriptId,
- requestBody: {
+ try {
+ await participantCreateMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
name: participantInput,
speaker: selectedText,
},
- })
- .then(() => {
- participants.refetch();
- setParticipantInput("");
- setOneMatch(undefined);
- setLoading(false);
- })
- .catch((e) => {
- setError(e, "There was an error creating");
- setLoading(false);
});
+ participants.refetch();
+ setParticipantInput("");
+ setOneMatch(undefined);
+ } catch (e) {
+ setError(e as Error, "There was an error creating");
+ }
} else if (
action == "Create and assign" &&
selectedTextIsTimeSlice(selectedText)
) {
- setLoading(true);
try {
- const participant = await api?.v1TranscriptAddParticipant({
- transcriptId,
- requestBody: {
+ const participant = await participantCreateMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
name: participantInput,
},
});
- setLoading(false);
assignTo(participant)().catch(() => {
// error and loading are handled by assignTo catch
participants.refetch();
});
} catch (error) {
- setError(e, "There was an error creating");
- setLoading(false);
+ setError(error as Error, "There was an error creating");
}
} else if (action == "Create") {
- setLoading(true);
- api
- ?.v1TranscriptAddParticipant({
- transcriptId,
- requestBody: {
+ try {
+ await participantCreateMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
name: participantInput,
},
- })
- .then(() => {
- participants.refetch();
- setParticipantInput("");
- setLoading(false);
- inputRef.current?.focus();
- })
- .catch((e) => {
- setError(e, "There was an error creating");
- setLoading(false);
});
+ participants.refetch();
+ setParticipantInput("");
+ inputRef.current?.focus();
+ } catch (e) {
+ setError(e as Error, "There was an error creating");
+ }
}
};
- const deleteParticipant = (participantId) => (e) => {
+ const deleteParticipant = (participantId) => async (e) => {
e.stopPropagation();
if (loading || participants.loading || topicWithWords.loading) return;
- setLoading(true);
- api
- ?.v1TranscriptDeleteParticipant({ transcriptId, participantId })
- .then(() => {
- participants.refetch();
- setLoading(false);
- })
- .catch((e) => {
- setError(e, "There was an error deleting");
- setLoading(false);
+
+ try {
+ await participantDeleteMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ participant_id: participantId,
+ },
+ },
});
+ participants.refetch();
+ } catch (e) {
+ setError(e as Error, "There was an error deleting");
+ }
};
const selectParticipant = (participant) => (e) => {
diff --git a/www/app/(app)/transcripts/[transcriptId]/correct/topicHeader.tsx b/www/app/(app)/transcripts/[transcriptId]/correct/topicHeader.tsx
index 1448de80..494d2929 100644
--- a/www/app/(app)/transcripts/[transcriptId]/correct/topicHeader.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/correct/topicHeader.tsx
@@ -1,6 +1,7 @@
import useTopics from "../../useTopics";
import { Dispatch, SetStateAction, useEffect } from "react";
-import { GetTranscriptTopic } from "../../../../api";
+import type { components } from "../../../../reflector-api";
+type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
import {
BoxProps,
Box,
diff --git a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx
index 4ce4a9e1..b1f61d43 100644
--- a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx
@@ -2,12 +2,10 @@ import { useEffect, useRef, useState } from "react";
import React from "react";
import Markdown from "react-markdown";
import "../../../styles/markdown.css";
-import {
- GetTranscript,
- GetTranscriptTopic,
- UpdateTranscript,
-} from "../../../api";
-import useApi from "../../../lib/useApi";
+import type { components } from "../../../reflector-api";
+type GetTranscript = components["schemas"]["GetTranscript"];
+type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
+import { useTranscriptUpdate } from "../../../lib/apiHooks";
import {
Flex,
Heading,
@@ -33,9 +31,8 @@ export default function FinalSummary(props: FinalSummaryProps) {
const [preEditSummary, setPreEditSummary] = useState("");
const [editedSummary, setEditedSummary] = useState("");
- const api = useApi();
-
const { setError } = useError();
+ const updateTranscriptMutation = useTranscriptUpdate();
useEffect(() => {
setEditedSummary(props.transcriptResponse?.long_summary || "");
@@ -47,12 +44,15 @@ export default function FinalSummary(props: FinalSummaryProps) {
const updateSummary = async (newSummary: string, transcriptId: string) => {
try {
- const requestBody: UpdateTranscript = {
- long_summary: newSummary,
- };
- const updatedTranscript = await api?.v1TranscriptUpdate({
- transcriptId,
- requestBody,
+ const updatedTranscript = await updateTranscriptMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: {
+ long_summary: newSummary,
+ },
});
if (props.onUpdate) {
props.onUpdate(newSummary);
@@ -60,7 +60,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
console.log("Updated long summary:", updatedTranscript);
} catch (err) {
console.error("Failed to update long summary:", err);
- setError(err, "Failed to update long summary.");
+ setError(err as Error, "Failed to update long summary.");
}
};
@@ -114,7 +114,12 @@ export default function FinalSummary(props: FinalSummaryProps) {
-
+
)}
{!isEditMode && (
diff --git a/www/app/(app)/transcripts/[transcriptId]/page.tsx b/www/app/(app)/transcripts/[transcriptId]/page.tsx
index 0a2dba47..ce48e951 100644
--- a/www/app/(app)/transcripts/[transcriptId]/page.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/page.tsx
@@ -86,7 +86,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
useActiveTopic={useActiveTopic}
waveform={waveform.waveform}
media={mp3.media}
- mediaDuration={transcript.response.duration}
+ mediaDuration={transcript.response?.duration || null}
/>
) : !mp3.loading && (waveform.error || mp3.error) ? (
@@ -116,7 +116,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
{
transcript.reload();
diff --git a/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx b/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx
index 3a13052e..567272ff 100644
--- a/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx
+++ b/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx
@@ -24,10 +24,16 @@ const TranscriptUpload = (details: TranscriptUpload) => {
const router = useRouter();
- const [status, setStatus] = useState(
+ const [status_, setStatus] = useState(
webSockets.status.value || transcript.response?.status || "idle",
);
+ // status is obviously done if we have transcript
+ const status =
+ !transcript.loading && transcript.response?.status === "ended"
+ ? transcript.response?.status
+ : status_;
+
useEffect(() => {
if (!transcriptStarted && webSockets.transcriptTextLive.length !== 0)
setTranscriptStarted(true);
@@ -35,8 +41,11 @@ const TranscriptUpload = (details: TranscriptUpload) => {
useEffect(() => {
//TODO HANDLE ERROR STATUS BETTER
+ // TODO deprecate webSockets.status.value / depend on transcript.response?.status from query lib
const newStatus =
- webSockets.status.value || transcript.response?.status || "idle";
+ transcript.response?.status === "ended"
+ ? "ended"
+ : webSockets.status.value || transcript.response?.status || "idle";
setStatus(newStatus);
if (newStatus && (newStatus == "ended" || newStatus == "error")) {
console.log(newStatus, "redirecting");
diff --git a/www/app/(app)/transcripts/createTranscript.ts b/www/app/(app)/transcripts/createTranscript.ts
index 015c82de..8a235161 100644
--- a/www/app/(app)/transcripts/createTranscript.ts
+++ b/www/app/(app)/transcripts/createTranscript.ts
@@ -1,45 +1,33 @@
-import { useEffect, useState } from "react";
+import type { components } from "../../reflector-api";
+import { useTranscriptCreate } from "../../lib/apiHooks";
-import { useError } from "../../(errors)/errorContext";
-import { CreateTranscript, GetTranscript } from "../../api";
-import useApi from "../../lib/useApi";
+type CreateTranscript = components["schemas"]["CreateTranscript"];
+type GetTranscript = components["schemas"]["GetTranscript"];
type UseCreateTranscript = {
transcript: GetTranscript | null;
loading: boolean;
error: Error | null;
- create: (transcriptCreationDetails: CreateTranscript) => void;
+ create: (transcriptCreationDetails: CreateTranscript) => Promise;
};
const useCreateTranscript = (): UseCreateTranscript => {
- const [transcript, setTranscript] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
+ const createMutation = useTranscriptCreate();
- const create = (transcriptCreationDetails: CreateTranscript) => {
- if (loading || !api) return;
+ const create = async (transcriptCreationDetails: CreateTranscript) => {
+ if (createMutation.isPending) return;
- setLoading(true);
-
- api
- .v1TranscriptsCreate({ requestBody: transcriptCreationDetails })
- .then((transcript) => {
- setTranscript(transcript);
- setLoading(false);
- })
- .catch((err) => {
- setError(
- err,
- "There was an issue creating a transcript, please try again.",
- );
- setErrorState(err);
- setLoading(false);
- });
+ await createMutation.mutateAsync({
+ body: transcriptCreationDetails,
+ });
};
- return { transcript, loading, error, create };
+ return {
+ transcript: createMutation.data || null,
+ loading: createMutation.isPending,
+ error: createMutation.error as Error | null,
+ create,
+ };
};
export default useCreateTranscript;
diff --git a/www/app/(app)/transcripts/fileUploadButton.tsx b/www/app/(app)/transcripts/fileUploadButton.tsx
index 1b4101e8..1f5d72eb 100644
--- a/www/app/(app)/transcripts/fileUploadButton.tsx
+++ b/www/app/(app)/transcripts/fileUploadButton.tsx
@@ -1,6 +1,7 @@
import React, { useState } from "react";
-import useApi from "../../lib/useApi";
+import { useTranscriptUploadAudio } from "../../lib/apiHooks";
import { Button, Spinner } from "@chakra-ui/react";
+import { useError } from "../../(errors)/errorContext";
type FileUploadButton = {
transcriptId: string;
@@ -8,13 +9,16 @@ type FileUploadButton = {
export default function FileUploadButton(props: FileUploadButton) {
const fileInputRef = React.useRef(null);
- const api = useApi();
+ const uploadMutation = useTranscriptUploadAudio();
+ const { setError } = useError();
const [progress, setProgress] = useState(0);
const triggerFileUpload = () => {
fileInputRef.current?.click();
};
- const handleFileUpload = (event: React.ChangeEvent) => {
+ const handleFileUpload = async (
+ event: React.ChangeEvent,
+ ) => {
const file = event.target.files?.[0];
if (file) {
@@ -24,37 +28,45 @@ export default function FileUploadButton(props: FileUploadButton) {
let start = 0;
let uploadedSize = 0;
- api?.httpRequest.config.interceptors.request.use((request) => {
- request.onUploadProgress = (progressEvent) => {
- const currentProgress = Math.floor(
- ((uploadedSize + progressEvent.loaded) / file.size) * 100,
- );
- setProgress(currentProgress);
- };
- return request;
- });
-
const uploadNextChunk = async () => {
- if (chunkNumber == totalChunks) return;
+ if (chunkNumber == totalChunks) {
+ setProgress(0);
+ return;
+ }
const chunkSize = Math.min(maxChunkSize, file.size - start);
const end = start + chunkSize;
const chunk = file.slice(start, end);
- await api?.v1TranscriptRecordUpload({
- transcriptId: props.transcriptId,
- formData: {
- chunk,
- },
- chunkNumber,
- totalChunks,
- });
+ try {
+ const formData = new FormData();
+ formData.append("chunk", chunk);
- uploadedSize += chunkSize;
- chunkNumber++;
- start = end;
+ await uploadMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: props.transcriptId,
+ },
+ query: {
+ chunk_number: chunkNumber,
+ total_chunks: totalChunks,
+ },
+ },
+ body: formData as any,
+ });
- uploadNextChunk();
+ uploadedSize += chunkSize;
+ const currentProgress = Math.floor((uploadedSize / file.size) * 100);
+ setProgress(currentProgress);
+
+ chunkNumber++;
+ start = end;
+
+ await uploadNextChunk();
+ } catch (error) {
+ setError(error as Error, "Failed to upload file");
+ setProgress(0);
+ }
};
uploadNextChunk();
diff --git a/www/app/(app)/transcripts/new/page.tsx b/www/app/(app)/transcripts/new/page.tsx
index 2670fd39..0410bd97 100644
--- a/www/app/(app)/transcripts/new/page.tsx
+++ b/www/app/(app)/transcripts/new/page.tsx
@@ -7,36 +7,29 @@ import About from "../../../(aboutAndPrivacy)/about";
import Privacy from "../../../(aboutAndPrivacy)/privacy";
import { useRouter } from "next/navigation";
import useCreateTranscript from "../createTranscript";
-import { SourceKind } from "../../../api";
import SelectSearch from "react-select-search";
import { supportedLanguages } from "../../../supportedLanguages";
-import useSessionStatus from "../../../lib/useSessionStatus";
import { featureEnabled } from "../../../domainContext";
-import { signIn } from "next-auth/react";
import {
Flex,
Box,
Spinner,
Heading,
Button,
- Card,
Center,
- Link,
- CardBody,
- Stack,
Text,
- Icon,
- Grid,
- IconButton,
Spacer,
- Menu,
- Tooltip,
- Input,
} from "@chakra-ui/react";
+import { useAuth } from "../../../lib/AuthProvider";
+import type { components } from "../../../reflector-api";
+
const TranscriptCreate = () => {
const isClient = typeof window !== "undefined";
const router = useRouter();
- const { isLoading, isAuthenticated } = useSessionStatus();
+ const auth = useAuth();
+ const isAuthenticated = auth.status === "authenticated";
+ const isAuthRefreshing = auth.status === "refreshing";
+ const isLoading = auth.status === "loading";
const requireLogin = featureEnabled("requireLogin");
const [name, setName] = useState("");
@@ -55,27 +48,31 @@ const TranscriptCreate = () => {
const [loadingUpload, setLoadingUpload] = useState(false);
const getTargetLanguage = () => {
- if (targetLanguage === "NOTRANSLATION") return;
+ if (targetLanguage === "NOTRANSLATION") return undefined;
return targetLanguage;
};
const send = () => {
if (loadingRecord || createTranscript.loading || permissionDenied) return;
setLoadingRecord(true);
+ const targetLang = getTargetLanguage();
createTranscript.create({
name,
- target_language: getTargetLanguage(),
- source_kind: "live" as SourceKind,
+ source_language: "en",
+ target_language: targetLang || "en",
+ source_kind: "live",
});
};
const uploadFile = () => {
if (loadingUpload || createTranscript.loading || permissionDenied) return;
setLoadingUpload(true);
+ const targetLang = getTargetLanguage();
createTranscript.create({
name,
- target_language: getTargetLanguage(),
- source_kind: "file" as SourceKind,
+ source_language: "en",
+ target_language: targetLang || "en",
+ source_kind: "file",
});
};
@@ -141,8 +138,8 @@ const TranscriptCreate = () => {
{isLoading ? (
- ) : requireLogin && !isAuthenticated ? (
-
+ ) : requireLogin && !isAuthenticated && !isAuthRefreshing ? (
+
) : (
{
- if (!api)
- throw new Error("ShareLink's API should always be ready at this point");
-
const selectedOption = shareOptionsData.find(
(option) => option.value === selectedValue,
);
@@ -67,19 +66,27 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
share_mode: selectedValue as "public" | "semi-private" | "private",
};
- const updatedTranscript = await api.v1TranscriptUpdate({
- transcriptId: props.transcriptResponse.id,
- requestBody,
- });
- setShareMode(
- shareOptionsData.find(
- (option) => option.value === updatedTranscript.share_mode,
- ) || shareOptionsData[0],
- );
- setShareLoading(false);
+ try {
+ const updatedTranscript = await updateTranscriptMutation.mutateAsync({
+ params: {
+ path: { transcript_id: props.transcriptResponse.id },
+ },
+ body: requestBody,
+ });
+ setShareMode(
+ shareOptionsData.find(
+ (option) => option.value === updatedTranscript.share_mode,
+ ) || shareOptionsData[0],
+ );
+ } catch (err) {
+ console.error("Failed to update share mode:", err);
+ } finally {
+ setShareLoading(false);
+ }
};
- const userId = useSessionUser().id;
+ const auth = useAuth();
+ const userId = auth.status === "authenticated" ? auth.user?.id : null;
useEffect(() => {
setIsOwner(!!(requireLogin && userId === props.transcriptResponse.user_id));
@@ -124,7 +131,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
"This transcript is public. Everyone can access it."}
- {isOwner && api && (
+ {isOwner && (
(undefined);
+ const [selectedStreamId, setSelectedStreamId] = useState(null);
const [topic, setTopic] = useState(undefined);
const [includeTopics, setIncludeTopics] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const [streams, setStreams] = useState([]);
- const [topics, setTopics] = useState([]);
- const api = useApi();
+
+ const { data: streams = [], isLoading: isLoadingStreams } = useZulipStreams();
+ const { data: topics = [] } = useZulipTopics(selectedStreamId);
+ const postToZulipMutation = useTranscriptPostToZulip();
+
const { contains } = useFilter({ sensitivity: "base" });
- const {
- collection: streamItemsCollection,
- filter: streamItemsFilter,
- set: streamItemsSet,
- } = useListCollection({
- initialItems: [] as { label: string; value: string }[],
- filter: contains,
- });
+ const streamItems = useMemo(() => {
+ return streams.map((stream: Stream) => ({
+ label: stream.name,
+ value: stream.name,
+ }));
+ }, [streams]);
- const {
- collection: topicItemsCollection,
- filter: topicItemsFilter,
- set: topicItemsSet,
- } = useListCollection({
- initialItems: [] as { label: string; value: string }[],
- filter: contains,
- });
+ const topicItems = useMemo(() => {
+ return topics.map(({ name }) => ({
+ label: name,
+ value: name,
+ }));
+ }, [topics]);
+ const { collection: streamItemsCollection, filter: streamItemsFilter } =
+ useListCollection({
+ initialItems: streamItems,
+ filter: contains,
+ });
+
+ const { collection: topicItemsCollection, filter: topicItemsFilter } =
+ useListCollection({
+ initialItems: topicItems,
+ filter: contains,
+ });
+
+ // Update selected stream ID when stream changes
useEffect(() => {
- const fetchZulipStreams = async () => {
- if (!api) return;
-
- try {
- const response = await api.v1ZulipGetStreams();
- setStreams(response);
-
- streamItemsSet(
- response.map((stream) => ({
- label: stream.name,
- value: stream.name,
- })),
- );
-
- setIsLoading(false);
- } catch (error) {
- console.error("Error fetching Zulip streams:", error);
- }
- };
-
- fetchZulipStreams();
- }, [!api]);
-
- useEffect(() => {
- const fetchZulipTopics = async () => {
- if (!api || !stream) return;
- try {
- const selectedStream = streams.find((s) => s.name === stream);
- if (selectedStream) {
- const response = await api.v1ZulipGetTopics({
- streamId: selectedStream.stream_id,
- });
- setTopics(response);
- topicItemsSet(
- response.map((topic) => ({
- label: topic.name,
- value: topic.name,
- })),
- );
- } else {
- topicItemsSet([]);
- }
- } catch (error) {
- console.error("Error fetching Zulip topics:", error);
- }
- };
-
- fetchZulipTopics();
- }, [stream, streams, api]);
+ if (stream && streams) {
+ const selectedStream = streams.find((s: Stream) => s.name === stream);
+ setSelectedStreamId(selectedStream ? selectedStream.stream_id : null);
+ } else {
+ setSelectedStreamId(null);
+ }
+ }, [stream, streams]);
const handleSendToZulip = async () => {
- if (!api || !props.transcriptResponse) return;
+ if (!props.transcriptResponse) return;
if (stream && topic) {
try {
- await api.v1TranscriptPostToZulip({
- transcriptId: props.transcriptResponse.id,
- stream,
- topic,
- includeTopics,
+ await postToZulipMutation.mutateAsync({
+ params: {
+ path: {
+ transcript_id: props.transcriptResponse.id,
+ },
+ query: {
+ stream,
+ topic,
+ include_topics: includeTopics,
+ },
+ },
});
setShowModal(false);
} catch (error) {
- console.log(error);
+ console.error("Error posting to Zulip:", error);
}
}
};
@@ -155,7 +132,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
- {isLoading ? (
+ {isLoadingStreams ? (
diff --git a/www/app/(app)/transcripts/transcriptTitle.tsx b/www/app/(app)/transcripts/transcriptTitle.tsx
index 4678818f..72421f48 100644
--- a/www/app/(app)/transcripts/transcriptTitle.tsx
+++ b/www/app/(app)/transcripts/transcriptTitle.tsx
@@ -1,6 +1,8 @@
import { useState } from "react";
-import { UpdateTranscript } from "../../api";
-import useApi from "../../lib/useApi";
+import type { components } from "../../reflector-api";
+
+type UpdateTranscript = components["schemas"]["UpdateTranscript"];
+import { useTranscriptUpdate } from "../../lib/apiHooks";
import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react";
import { LuPen } from "react-icons/lu";
@@ -14,24 +16,27 @@ const TranscriptTitle = (props: TranscriptTitle) => {
const [displayedTitle, setDisplayedTitle] = useState(props.title);
const [preEditTitle, setPreEditTitle] = useState(props.title);
const [isEditing, setIsEditing] = useState(false);
- const api = useApi();
+ const updateTranscriptMutation = useTranscriptUpdate();
const updateTitle = async (newTitle: string, transcriptId: string) => {
- if (!api) return;
try {
const requestBody: UpdateTranscript = {
title: newTitle,
};
- const updatedTranscript = await api?.v1TranscriptUpdate({
- transcriptId,
- requestBody,
+ await updateTranscriptMutation.mutateAsync({
+ params: {
+ path: { transcript_id: transcriptId },
+ },
+ body: requestBody,
});
if (props.onUpdate) {
props.onUpdate(newTitle);
}
- console.log("Updated transcript:", updatedTranscript);
+ console.log("Updated transcript title:", newTitle);
} catch (err) {
console.error("Failed to update transcript:", err);
+ // Revert title on error
+ setDisplayedTitle(preEditTitle);
}
};
diff --git a/www/app/(app)/transcripts/useMp3.ts b/www/app/(app)/transcripts/useMp3.ts
index 3e8344ad..223a9a4a 100644
--- a/www/app/(app)/transcripts/useMp3.ts
+++ b/www/app/(app)/transcripts/useMp3.ts
@@ -1,6 +1,7 @@
import { useContext, useEffect, useState } from "react";
import { DomainContext } from "../../domainContext";
-import getApi from "../../lib/useApi";
+import { useTranscriptGet } from "../../lib/apiHooks";
+import { useAuth } from "../../lib/AuthProvider";
export type Mp3Response = {
media: HTMLMediaElement | null;
@@ -17,14 +18,17 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
const [audioLoadingError, setAudioLoadingError] = useState(
null,
);
- const [transcriptMetadataLoading, setTranscriptMetadataLoading] =
- useState(true);
- const [transcriptMetadataLoadingError, setTranscriptMetadataLoadingError] =
- useState(null);
const [audioDeleted, setAudioDeleted] = useState(null);
- const api = getApi();
const { api_url } = useContext(DomainContext);
- const accessTokenInfo = api?.httpRequest?.config?.TOKEN;
+ const auth = useAuth();
+ const accessTokenInfo =
+ auth.status === "authenticated" ? auth.accessToken : null;
+
+ const {
+ data: transcript,
+ isLoading: transcriptMetadataLoading,
+ error: transcriptError,
+ } = useTranscriptGet(later ? null : transcriptId);
const [serviceWorker, setServiceWorker] =
useState(null);
@@ -52,72 +56,50 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
}, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]);
useEffect(() => {
- if (!transcriptId || !api || later) return;
+ if (!transcriptId || later || !transcript) return;
let stopped = false;
let audioElement: HTMLAudioElement | null = null;
let handleCanPlay: (() => void) | null = null;
let handleError: (() => void) | null = null;
- setTranscriptMetadataLoading(true);
setAudioLoading(true);
- // First fetch transcript info to check if audio is deleted
- api
- .v1TranscriptGet({ transcriptId })
- .then((transcript) => {
- if (stopped) {
- return;
- }
+ const deleted = transcript.audio_deleted || false;
+ setAudioDeleted(deleted);
- const deleted = transcript.audio_deleted || false;
- setAudioDeleted(deleted);
- setTranscriptMetadataLoadingError(null);
+ if (deleted) {
+ // Audio is deleted, don't attempt to load it
+ setMedia(null);
+ setAudioLoadingError(null);
+ setAudioLoading(false);
+ return;
+ }
- if (deleted) {
- // Audio is deleted, don't attempt to load it
- setMedia(null);
- setAudioLoadingError(null);
- setAudioLoading(false);
- return;
- }
+ // Audio is not deleted, proceed to load it
+ audioElement = document.createElement("audio");
+ audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`;
+ audioElement.crossOrigin = "anonymous";
+ audioElement.preload = "auto";
- // Audio is not deleted, proceed to load it
- audioElement = document.createElement("audio");
- audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`;
- audioElement.crossOrigin = "anonymous";
- audioElement.preload = "auto";
+ handleCanPlay = () => {
+ if (stopped) return;
+ setAudioLoading(false);
+ setAudioLoadingError(null);
+ };
- handleCanPlay = () => {
- if (stopped) return;
- setAudioLoading(false);
- setAudioLoadingError(null);
- };
+ handleError = () => {
+ if (stopped) return;
+ setAudioLoading(false);
+ setAudioLoadingError("Failed to load audio");
+ };
- handleError = () => {
- if (stopped) return;
- setAudioLoading(false);
- setAudioLoadingError("Failed to load audio");
- };
+ audioElement.addEventListener("canplay", handleCanPlay);
+ audioElement.addEventListener("error", handleError);
- audioElement.addEventListener("canplay", handleCanPlay);
- audioElement.addEventListener("error", handleError);
-
- if (!stopped) {
- setMedia(audioElement);
- }
- })
- .catch((error) => {
- if (stopped) return;
- console.error("Failed to fetch transcript:", error);
- setAudioDeleted(null);
- setTranscriptMetadataLoadingError(error.message);
- setAudioLoading(false);
- })
- .finally(() => {
- if (stopped) return;
- setTranscriptMetadataLoading(false);
- });
+ if (!stopped) {
+ setMedia(audioElement);
+ }
return () => {
stopped = true;
@@ -128,14 +110,18 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
if (handleError) audioElement.removeEventListener("error", handleError);
}
};
- }, [transcriptId, api, later, api_url]);
+ }, [transcriptId, transcript, later, api_url]);
const getNow = () => {
setLater(false);
};
const loading = audioLoading || transcriptMetadataLoading;
- const error = audioLoadingError || transcriptMetadataLoadingError;
+ const error =
+ audioLoadingError ||
+ (transcriptError
+ ? (transcriptError as any).message || String(transcriptError)
+ : null);
return { media, loading, error, getNow, audioDeleted };
};
diff --git a/www/app/(app)/transcripts/useParticipants.ts b/www/app/(app)/transcripts/useParticipants.ts
index 38f5aa35..a3674597 100644
--- a/www/app/(app)/transcripts/useParticipants.ts
+++ b/www/app/(app)/transcripts/useParticipants.ts
@@ -1,8 +1,6 @@
-import { useEffect, useState } from "react";
-import { Participant } from "../../api";
-import { useError } from "../../(errors)/errorContext";
-import useApi from "../../lib/useApi";
-import { shouldShowError } from "../../lib/errorUtils";
+import type { components } from "../../reflector-api";
+type Participant = components["schemas"]["Participant"];
+import { useTranscriptParticipants } from "../../lib/apiHooks";
type ErrorParticipants = {
error: Error;
@@ -29,46 +27,38 @@ export type UseParticipants = (
) & { refetch: () => void };
const useParticipants = (transcriptId: string): UseParticipants => {
- const [response, setResponse] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
- const [count, setCount] = useState(0);
+ const {
+ data: response,
+ isLoading: loading,
+ error,
+ refetch,
+ } = useTranscriptParticipants(transcriptId || null);
- const refetch = () => {
- if (!loading) {
- setCount(count + 1);
- setLoading(true);
- setErrorState(null);
- }
- };
+ // Type-safe return based on state
+ if (error) {
+ return {
+ error: error as Error,
+ loading: false,
+ response: null,
+ refetch,
+ } satisfies ErrorParticipants & { refetch: () => void };
+ }
- useEffect(() => {
- if (!transcriptId || !api) return;
+ if (loading || !response) {
+ return {
+ response: response || null,
+ loading: true,
+ error: null,
+ refetch,
+ } satisfies LoadingParticipants & { refetch: () => void };
+ }
- setLoading(true);
- api
- .v1TranscriptGetParticipants({ transcriptId })
- .then((result) => {
- setResponse(result);
- setLoading(false);
- console.debug("Participants Loaded:", result);
- })
- .catch((error) => {
- const shouldShowHuman = shouldShowError(error);
- if (shouldShowHuman) {
- setError(error, "There was an error loading the participants");
- } else {
- setError(error);
- }
- setErrorState(error);
- setResponse(null);
- setLoading(false);
- });
- }, [transcriptId, !api, count]);
-
- return { response, loading, error, refetch } as UseParticipants;
+ return {
+ response,
+ loading: false,
+ error: null,
+ refetch,
+ } satisfies SuccessParticipants & { refetch: () => void };
};
export default useParticipants;
diff --git a/www/app/(app)/transcripts/useSearchTranscripts.ts b/www/app/(app)/transcripts/useSearchTranscripts.ts
deleted file mode 100644
index 2e6a7311..00000000
--- a/www/app/(app)/transcripts/useSearchTranscripts.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-// this hook is not great, we want to substitute it with a proper state management solution that is also not re-invention
-
-import { useEffect, useRef, useState } from "react";
-import { SearchResult, SourceKind } from "../../api";
-import useApi from "../../lib/useApi";
-import {
- PaginationPage,
- paginationPageTo0Based,
-} from "../browse/_components/Pagination";
-
-interface SearchFilters {
- roomIds: readonly string[] | null;
- sourceKind: SourceKind | null;
-}
-
-const EMPTY_SEARCH_FILTERS: SearchFilters = {
- roomIds: null,
- sourceKind: null,
-};
-
-type UseSearchTranscriptsOptions = {
- pageSize: number;
- page: PaginationPage;
-};
-
-interface UseSearchTranscriptsReturn {
- results: SearchResult[];
- totalCount: number;
- isLoading: boolean;
- error: unknown;
- reload: () => void;
-}
-
-function hashEffectFilters(filters: SearchFilters): string {
- return JSON.stringify(filters);
-}
-
-export function useSearchTranscripts(
- query: string = "",
- filters: SearchFilters = EMPTY_SEARCH_FILTERS,
- options: UseSearchTranscriptsOptions = {
- pageSize: 20,
- page: PaginationPage(1),
- },
-): UseSearchTranscriptsReturn {
- const { pageSize, page } = options;
-
- const [reloadCount, setReloadCount] = useState(0);
-
- const api = useApi();
- const abortControllerRef = useRef();
-
- const [data, setData] = useState<{ results: SearchResult[]; total: number }>({
- results: [],
- total: 0,
- });
- const [error, setError] = useState();
- const [isLoading, setIsLoading] = useState(false);
-
- const filterHash = hashEffectFilters(filters);
-
- useEffect(() => {
- if (!api) {
- setData({ results: [], total: 0 });
- setError(undefined);
- setIsLoading(false);
- return;
- }
-
- if (abortControllerRef.current) {
- abortControllerRef.current.abort();
- }
-
- const abortController = new AbortController();
- abortControllerRef.current = abortController;
-
- const performSearch = async () => {
- setIsLoading(true);
-
- try {
- const response = await api.v1TranscriptsSearch({
- q: query || "",
- limit: pageSize,
- offset: paginationPageTo0Based(page) * pageSize,
- roomId: filters.roomIds?.[0],
- sourceKind: filters.sourceKind || undefined,
- });
-
- if (abortController.signal.aborted) return;
- setData(response);
- setError(undefined);
- } catch (err: unknown) {
- if ((err as Error).name === "AbortError") {
- return;
- }
- if (abortController.signal.aborted) {
- console.error("Aborted search but error", err);
- return;
- }
-
- setError(err);
- } finally {
- if (!abortController.signal.aborted) {
- setIsLoading(false);
- }
- }
- };
-
- performSearch().then(() => {});
-
- return () => {
- abortController.abort();
- };
- }, [api, query, page, filterHash, pageSize, reloadCount]);
-
- return {
- results: data.results,
- totalCount: data.total,
- isLoading,
- error,
- reload: () => setReloadCount(reloadCount + 1),
- };
-}
diff --git a/www/app/(app)/transcripts/useTopicWithWords.ts b/www/app/(app)/transcripts/useTopicWithWords.ts
index 29d0b982..31e184cc 100644
--- a/www/app/(app)/transcripts/useTopicWithWords.ts
+++ b/www/app/(app)/transcripts/useTopicWithWords.ts
@@ -1,9 +1,8 @@
-import { useEffect, useState } from "react";
+import type { components } from "../../reflector-api";
+import { useTranscriptTopicsWithWordsPerSpeaker } from "../../lib/apiHooks";
-import { GetTranscriptTopicWithWordsPerSpeaker } from "../../api";
-import { useError } from "../../(errors)/errorContext";
-import useApi from "../../lib/useApi";
-import { shouldShowError } from "../../lib/errorUtils";
+type GetTranscriptTopicWithWordsPerSpeaker =
+ components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"];
type ErrorTopicWithWords = {
error: Error;
@@ -33,47 +32,40 @@ const useTopicWithWords = (
topicId: string | undefined,
transcriptId: string,
): UseTopicWithWords => {
- const [response, setResponse] =
- useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
+ const {
+ data: response,
+ isLoading: loading,
+ error,
+ refetch,
+ } = useTranscriptTopicsWithWordsPerSpeaker(
+ transcriptId || null,
+ topicId || null,
+ );
- const [count, setCount] = useState(0);
+ if (error) {
+ return {
+ error: error as Error,
+ loading: false,
+ response: null,
+ refetch,
+ } satisfies ErrorTopicWithWords & { refetch: () => void };
+ }
- const refetch = () => {
- if (!loading) {
- setCount(count + 1);
- setLoading(true);
- setErrorState(null);
- }
- };
+ if (loading || !response) {
+ return {
+ response: response || null,
+ loading: true,
+ error: false,
+ refetch,
+ } satisfies LoadingTopicWithWords & { refetch: () => void };
+ }
- useEffect(() => {
- if (!transcriptId || !topicId || !api) return;
-
- setLoading(true);
-
- api
- .v1TranscriptGetTopicsWithWordsPerSpeaker({ transcriptId, topicId })
- .then((result) => {
- setResponse(result);
- setLoading(false);
- console.debug("Topics with words Loaded:", result);
- })
- .catch((error) => {
- const shouldShowHuman = shouldShowError(error);
- if (shouldShowHuman) {
- setError(error, "There was an error loading the topics with words");
- } else {
- setError(error);
- }
- setErrorState(error);
- });
- }, [transcriptId, !api, topicId, count]);
-
- return { response, loading, error, refetch } as UseTopicWithWords;
+ return {
+ response,
+ loading: false,
+ error: null,
+ refetch,
+ } satisfies SuccessTopicWithWords & { refetch: () => void };
};
export default useTopicWithWords;
diff --git a/www/app/(app)/transcripts/useTopics.ts b/www/app/(app)/transcripts/useTopics.ts
index ff17beaf..7f337582 100644
--- a/www/app/(app)/transcripts/useTopics.ts
+++ b/www/app/(app)/transcripts/useTopics.ts
@@ -1,10 +1,7 @@
-import { useEffect, useState } from "react";
+import { useTranscriptTopics } from "../../lib/apiHooks";
+import type { components } from "../../reflector-api";
-import { useError } from "../../(errors)/errorContext";
-import { Topic } from "./webSocketTypes";
-import useApi from "../../lib/useApi";
-import { shouldShowError } from "../../lib/errorUtils";
-import { GetTranscriptTopic } from "../../api";
+type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
type TranscriptTopics = {
topics: GetTranscriptTopic[] | null;
@@ -13,34 +10,13 @@ type TranscriptTopics = {
};
const useTopics = (id: string): TranscriptTopics => {
- const [topics, setTopics] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
- useEffect(() => {
- if (!id || !api) return;
+ const { data: topics, isLoading: loading, error } = useTranscriptTopics(id);
- setLoading(true);
- api
- .v1TranscriptGetTopics({ transcriptId: id })
- .then((result) => {
- setTopics(result);
- setLoading(false);
- console.debug("Transcript topics loaded:", result);
- })
- .catch((err) => {
- setErrorState(err);
- const shouldShowHuman = shouldShowError(err);
- if (shouldShowHuman) {
- setError(err, "There was an error loading the topics");
- } else {
- setError(err);
- }
- });
- }, [id, !api]);
-
- return { topics, loading, error };
+ return {
+ topics: topics || null,
+ loading,
+ error: error as Error | null,
+ };
};
export default useTopics;
diff --git a/www/app/(app)/transcripts/useTranscript.ts b/www/app/(app)/transcripts/useTranscript.ts
index 49d257f0..3e56fb9e 100644
--- a/www/app/(app)/transcripts/useTranscript.ts
+++ b/www/app/(app)/transcripts/useTranscript.ts
@@ -1,8 +1,7 @@
-import { useEffect, useState } from "react";
-import { GetTranscript } from "../../api";
-import { useError } from "../../(errors)/errorContext";
-import { shouldShowError } from "../../lib/errorUtils";
-import useApi from "../../lib/useApi";
+import type { components } from "../../reflector-api";
+import { useTranscriptGet } from "../../lib/apiHooks";
+
+type GetTranscript = components["schemas"]["GetTranscript"];
type ErrorTranscript = {
error: Error;
@@ -28,43 +27,43 @@ type SuccessTranscript = {
const useTranscript = (
id: string | null,
): ErrorTranscript | LoadingTranscript | SuccessTranscript => {
- const [response, setResponse] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setErrorState] = useState(null);
- const [reload, setReload] = useState(0);
- const { setError } = useError();
- const api = useApi();
- const reloadHandler = () => setReload((prev) => prev + 1);
+ const { data, isLoading, error, refetch } = useTranscriptGet(id);
- useEffect(() => {
- if (!id || !api) return;
+ // Map to the expected return format
+ if (isLoading) {
+ return {
+ response: null,
+ loading: true,
+ error: false,
+ reload: refetch,
+ };
+ }
- if (!response) {
- setLoading(true);
- }
+ if (error) {
+ return {
+ error: error as Error,
+ loading: false,
+ response: null,
+ reload: refetch,
+ };
+ }
- api
- .v1TranscriptGet({ transcriptId: id })
- .then((result) => {
- setResponse(result);
- setLoading(false);
- console.debug("Transcript Loaded:", result);
- })
- .catch((error) => {
- const shouldShowHuman = shouldShowError(error);
- if (shouldShowHuman) {
- setError(error, "There was an error loading the transcript");
- } else {
- setError(error);
- }
- setErrorState(error);
- });
- }, [id, !api, reload]);
+ // Check if data is undefined or null
+ if (!data) {
+ return {
+ response: null,
+ loading: true,
+ error: false,
+ reload: refetch,
+ };
+ }
- return { response, loading, error, reload: reloadHandler } as
- | ErrorTranscript
- | LoadingTranscript
- | SuccessTranscript;
+ return {
+ response: data,
+ loading: false,
+ error: null,
+ reload: refetch,
+ };
};
export default useTranscript;
diff --git a/www/app/(app)/transcripts/useWaveform.ts b/www/app/(app)/transcripts/useWaveform.ts
index 19b2a265..8bb8c4c9 100644
--- a/www/app/(app)/transcripts/useWaveform.ts
+++ b/www/app/(app)/transcripts/useWaveform.ts
@@ -1,8 +1,7 @@
-import { useEffect, useState } from "react";
-import { AudioWaveform } from "../../api";
-import { useError } from "../../(errors)/errorContext";
-import useApi from "../../lib/useApi";
-import { shouldShowError } from "../../lib/errorUtils";
+import type { components } from "../../reflector-api";
+import { useTranscriptWaveform } from "../../lib/apiHooks";
+
+type AudioWaveform = components["schemas"]["AudioWaveform"];
type AudioWaveFormResponse = {
waveform: AudioWaveform | null;
@@ -11,35 +10,17 @@ type AudioWaveFormResponse = {
};
const useWaveform = (id: string, skip: boolean): AudioWaveFormResponse => {
- const [waveform, setWaveform] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setErrorState] = useState(null);
- const { setError } = useError();
- const api = useApi();
+ const {
+ data: waveform,
+ isLoading: loading,
+ error,
+ } = useTranscriptWaveform(skip ? null : id);
- useEffect(() => {
- if (!id || !api || skip) {
- setLoading(false);
- setErrorState(null);
- setWaveform(null);
- return;
- }
- setLoading(true);
- setErrorState(null);
- api
- .v1TranscriptGetAudioWaveform({ transcriptId: id })
- .then((result) => {
- setWaveform(result);
- setLoading(false);
- console.debug("Transcript waveform loaded:", result);
- })
- .catch((err) => {
- setErrorState(err);
- setLoading(false);
- });
- }, [id, api, skip]);
-
- return { waveform, loading, error };
+ return {
+ waveform: waveform || null,
+ loading,
+ error: error as Error | null,
+ };
};
export default useWaveform;
diff --git a/www/app/(app)/transcripts/useWebRTC.ts b/www/app/(app)/transcripts/useWebRTC.ts
index c8370aa4..89a2a946 100644
--- a/www/app/(app)/transcripts/useWebRTC.ts
+++ b/www/app/(app)/transcripts/useWebRTC.ts
@@ -1,8 +1,9 @@
import { useEffect, useState } from "react";
import Peer from "simple-peer";
import { useError } from "../../(errors)/errorContext";
-import useApi from "../../lib/useApi";
-import { RtcOffer } from "../../api";
+import { useTranscriptWebRTC } from "../../lib/apiHooks";
+import type { components } from "../../reflector-api";
+type RtcOffer = components["schemas"]["RtcOffer"];
const useWebRTC = (
stream: MediaStream | null,
@@ -10,10 +11,10 @@ const useWebRTC = (
): Peer => {
const [peer, setPeer] = useState(null);
const { setError } = useError();
- const api = useApi();
+ const { mutateAsync: mutateWebRtcTranscriptAsync } = useTranscriptWebRTC();
useEffect(() => {
- if (!stream || !transcriptId || !api) {
+ if (!stream || !transcriptId) {
return;
}
@@ -24,7 +25,7 @@ const useWebRTC = (
try {
p = new Peer({ initiator: true, stream: stream });
} catch (error) {
- setError(error, "Error creating WebRTC");
+ setError(error as Error, "Error creating WebRTC");
return;
}
@@ -32,26 +33,31 @@ const useWebRTC = (
setError(new Error(`WebRTC error: ${err}`));
});
- p.on("signal", (data: any) => {
- if (!api) return;
+ p.on("signal", async (data: any) => {
if ("sdp" in data) {
const rtcOffer: RtcOffer = {
sdp: data.sdp,
type: data.type,
};
- api
- .v1TranscriptRecordWebrtc({ transcriptId, requestBody: rtcOffer })
- .then((answer) => {
- try {
- p.signal(answer);
- } catch (error) {
- setError(error);
- }
- })
- .catch((error) => {
- setError(error, "Error loading WebRTCOffer");
+ try {
+ const answer = await mutateWebRtcTranscriptAsync({
+ params: {
+ path: {
+ transcript_id: transcriptId,
+ },
+ },
+ body: rtcOffer,
});
+
+ try {
+ p.signal(answer);
+ } catch (error) {
+ setError(error as Error);
+ }
+ } catch (error) {
+ setError(error as Error, "Error loading WebRTCOffer");
+ }
}
});
@@ -63,7 +69,7 @@ const useWebRTC = (
return () => {
p.destroy();
};
- }, [stream, transcriptId, !api]);
+ }, [stream, transcriptId, mutateWebRtcTranscriptAsync]);
return peer;
};
diff --git a/www/app/(app)/transcripts/useWebSockets.ts b/www/app/(app)/transcripts/useWebSockets.ts
index 6fa5edc7..2b3205c4 100644
--- a/www/app/(app)/transcripts/useWebSockets.ts
+++ b/www/app/(app)/transcripts/useWebSockets.ts
@@ -2,8 +2,12 @@ import { useContext, useEffect, useState } from "react";
import { Topic, FinalSummary, Status } from "./webSocketTypes";
import { useError } from "../../(errors)/errorContext";
import { DomainContext } from "../../domainContext";
-import { AudioWaveform, GetTranscriptSegmentTopic } from "../../api";
-import useApi from "../../lib/useApi";
+import type { components } from "../../reflector-api";
+type AudioWaveform = components["schemas"]["AudioWaveform"];
+type GetTranscriptSegmentTopic =
+ components["schemas"]["GetTranscriptSegmentTopic"];
+import { useQueryClient } from "@tanstack/react-query";
+import { $api } from "../../lib/apiClient";
export type UseWebSockets = {
transcriptTextLive: string;
@@ -33,8 +37,8 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
const [status, setStatus] = useState({ value: "" });
const { setError } = useError();
- const { websocket_url } = useContext(DomainContext);
- const api = useApi();
+ const { websocket_url: websocketUrl } = useContext(DomainContext);
+ const queryClient = useQueryClient();
const [accumulatedText, setAccumulatedText] = useState("");
@@ -105,6 +109,13 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
title: "Topic 1: Introduction to Quantum Mechanics",
transcript:
"A brief overview of quantum mechanics and its principles.",
+ segments: [
+ {
+ speaker: 1,
+ start: 0,
+ text: "This is the transcription of an example title",
+ },
+ ],
},
{
id: "2",
@@ -315,11 +326,9 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
}
};
- if (!transcriptId || !api) return;
+ if (!transcriptId) return;
- api?.v1TranscriptGetWebsocketEvents({ transcriptId }).then((result) => {});
-
- const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`;
+ const url = `${websocketUrl}/v1/transcripts/${transcriptId}/events`;
let ws = new WebSocket(url);
ws.onopen = () => {
@@ -361,6 +370,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
return [...prevTopics, topic];
});
console.debug("TOPIC event:", message.data);
+ // Invalidate topics query to sync with WebSocket data
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/topics",
+ {
+ params: { path: { transcript_id: transcriptId } },
+ },
+ ).queryKey,
+ });
break;
case "FINAL_SHORT_SUMMARY":
@@ -370,6 +389,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
case "FINAL_LONG_SUMMARY":
if (message.data) {
setFinalSummary(message.data);
+ // Invalidate transcript query to sync summary
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: { path: { transcript_id: transcriptId } },
+ },
+ ).queryKey,
+ });
}
break;
@@ -377,6 +406,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
console.debug("FINAL_TITLE event:", message.data);
if (message.data) {
setTitle(message.data.title);
+ // Invalidate transcript query to sync title
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: { path: { transcript_id: transcriptId } },
+ },
+ ).queryKey,
+ });
}
break;
@@ -434,6 +473,11 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
break;
case 1001: // Navigate away
break;
+ case 1006: // Closed by client Chrome
+ console.warn(
+ "WebSocket closed by client, likely duplicated connection in react dev mode",
+ );
+ break;
default:
setError(
new Error(`WebSocket closed unexpectedly with code: ${event.code}`),
@@ -450,7 +494,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
return () => {
ws.close();
};
- }, [transcriptId, !api]);
+ }, [transcriptId, websocketUrl]);
return {
transcriptTextLive,
diff --git a/www/app/(app)/transcripts/webSocketTypes.ts b/www/app/(app)/transcripts/webSocketTypes.ts
index edd35eb6..4ec98946 100644
--- a/www/app/(app)/transcripts/webSocketTypes.ts
+++ b/www/app/(app)/transcripts/webSocketTypes.ts
@@ -1,4 +1,6 @@
-import { GetTranscriptTopic } from "../../api";
+import type { components } from "../../reflector-api";
+
+type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
export type Topic = GetTranscriptTopic;
diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx
index ffb286b3..bf6a5b62 100644
--- a/www/app/(auth)/userInfo.tsx
+++ b/www/app/(auth)/userInfo.tsx
@@ -1,18 +1,21 @@
"use client";
-import { signOut, signIn } from "next-auth/react";
-import useSessionStatus from "../lib/useSessionStatus";
+
import { Spinner, Link } from "@chakra-ui/react";
+import { useAuth } from "../lib/AuthProvider";
export default function UserInfo() {
- const { isLoading, isAuthenticated } = useSessionStatus();
-
+ const auth = useAuth();
+ const status = auth.status;
+ const isLoading = status === "loading";
+ const isAuthenticated = status === "authenticated";
+ const isRefreshing = status === "refreshing";
return isLoading ? (
- ) : !isAuthenticated ? (
+ ) : !isAuthenticated && !isRefreshing ? (
signIn("authentik")}
+ onClick={() => auth.signIn("authentik")}
>
Log in
@@ -20,7 +23,7 @@ export default function UserInfo() {
signOut({ callbackUrl: "/" })}
+ onClick={() => auth.signOut({ callbackUrl: "/" })}
>
Log out
diff --git a/www/app/[roomName]/page.tsx b/www/app/[roomName]/page.tsx
index b03a7e4f..0130588b 100644
--- a/www/app/[roomName]/page.tsx
+++ b/www/app/[roomName]/page.tsx
@@ -21,11 +21,13 @@ import { toaster } from "../components/ui/toaster";
import useRoomMeeting from "./useRoomMeeting";
import { useRouter } from "next/navigation";
import { notFound } from "next/navigation";
-import useSessionStatus from "../lib/useSessionStatus";
import { useRecordingConsent } from "../recordingConsentContext";
-import useApi from "../lib/useApi";
-import { Meeting } from "../api";
+import { useMeetingAudioConsent } from "../lib/apiHooks";
+import type { components } from "../reflector-api";
+
+type Meeting = components["schemas"]["Meeting"];
import { FaBars } from "react-icons/fa6";
+import { useAuth } from "../lib/AuthProvider";
export type RoomDetails = {
params: {
@@ -76,31 +78,30 @@ const useConsentDialog = (
wherebyRef: RefObject /*accessibility*/,
) => {
const { state: consentState, touch, hasConsent } = useRecordingConsent();
- const [consentLoading, setConsentLoading] = useState(false);
// toast would open duplicates, even with using "id=" prop
const [modalOpen, setModalOpen] = useState(false);
- const api = useApi();
+ const audioConsentMutation = useMeetingAudioConsent();
const handleConsent = useCallback(
async (meetingId: string, given: boolean) => {
- if (!api) return;
-
- setConsentLoading(true);
-
try {
- await api.v1MeetingAudioConsent({
- meetingId,
- requestBody: { consent_given: given },
+ await audioConsentMutation.mutateAsync({
+ params: {
+ path: {
+ meeting_id: meetingId,
+ },
+ },
+ body: {
+ consent_given: given,
+ },
});
touch(meetingId);
} catch (error) {
console.error("Error submitting consent:", error);
- } finally {
- setConsentLoading(false);
}
},
- [api, touch],
+ [audioConsentMutation, touch],
);
const showConsentModal = useCallback(() => {
@@ -194,7 +195,12 @@ const useConsentDialog = (
return cleanup;
}, [meetingId, handleConsent, wherebyRef, modalOpen]);
- return { showConsentModal, consentState, hasConsent, consentLoading };
+ return {
+ showConsentModal,
+ consentState,
+ hasConsent,
+ consentLoading: audioConsentMutation.isPending,
+ };
};
function ConsentDialogButton({
@@ -254,7 +260,9 @@ export default function Room(details: RoomDetails) {
const roomName = details.params.roomName;
const meeting = useRoomMeeting(roomName);
const router = useRouter();
- const { isLoading, isAuthenticated } = useSessionStatus();
+ const status = useAuth().status;
+ const isAuthenticated = status === "authenticated";
+ const isLoading = status === "loading" || meeting.loading;
const roomUrl = meeting?.response?.host_room_url
? meeting?.response?.host_room_url
diff --git a/www/app/[roomName]/useRoomMeeting.tsx b/www/app/[roomName]/useRoomMeeting.tsx
index 98c2f1f2..93491a05 100644
--- a/www/app/[roomName]/useRoomMeeting.tsx
+++ b/www/app/[roomName]/useRoomMeeting.tsx
@@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import { useError } from "../(errors)/errorContext";
-import { Meeting } from "../api";
+import type { components } from "../reflector-api";
import { shouldShowError } from "../lib/errorUtils";
-import useApi from "../lib/useApi";
+
+type Meeting = components["schemas"]["Meeting"];
+import { useRoomsCreateMeeting } from "../lib/apiHooks";
import { notFound } from "next/navigation";
type ErrorMeeting = {
@@ -30,27 +32,25 @@ const useRoomMeeting = (
roomName: string | null | undefined,
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
const [response, setResponse] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setErrorState] = useState(null);
const [reload, setReload] = useState(0);
const { setError } = useError();
- const api = useApi();
+ const createMeetingMutation = useRoomsCreateMeeting();
const reloadHandler = () => setReload((prev) => prev + 1);
useEffect(() => {
- if (!roomName || !api) return;
+ if (!roomName) return;
- if (!response) {
- setLoading(true);
- }
-
- api
- .v1RoomsCreateMeeting({ roomName })
- .then((result) => {
+ const createMeeting = async () => {
+ try {
+ const result = await createMeetingMutation.mutateAsync({
+ params: {
+ path: {
+ room_name: roomName,
+ },
+ },
+ });
setResponse(result);
- setLoading(false);
- })
- .catch((error) => {
+ } catch (error: any) {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman && error.status !== 404) {
setError(
@@ -60,9 +60,14 @@ const useRoomMeeting = (
} else {
setError(error);
}
- setErrorState(error);
- });
- }, [roomName, !api, reload]);
+ }
+ };
+
+ createMeeting();
+ }, [roomName, reload]);
+
+ const loading = createMeetingMutation.isPending && !response;
+ const error = createMeetingMutation.error as Error | null;
return { response, loading, error, reload: reloadHandler } as
| ErrorMeeting
diff --git a/www/app/api/OpenApi.ts b/www/app/api/OpenApi.ts
deleted file mode 100644
index 23cc35f3..00000000
--- a/www/app/api/OpenApi.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { BaseHttpRequest } from "./core/BaseHttpRequest";
-import type { OpenAPIConfig } from "./core/OpenAPI";
-import { Interceptors } from "./core/OpenAPI";
-import { AxiosHttpRequest } from "./core/AxiosHttpRequest";
-
-import { DefaultService } from "./services.gen";
-
-type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
-
-export class OpenApi {
- public readonly default: DefaultService;
-
- public readonly request: BaseHttpRequest;
-
- constructor(
- config?: Partial,
- HttpRequest: HttpRequestConstructor = AxiosHttpRequest,
- ) {
- this.request = new HttpRequest({
- BASE: config?.BASE ?? "",
- VERSION: config?.VERSION ?? "0.1.0",
- WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
- CREDENTIALS: config?.CREDENTIALS ?? "include",
- TOKEN: config?.TOKEN,
- USERNAME: config?.USERNAME,
- PASSWORD: config?.PASSWORD,
- HEADERS: config?.HEADERS,
- ENCODE_PATH: config?.ENCODE_PATH,
- interceptors: {
- request: config?.interceptors?.request ?? new Interceptors(),
- response: config?.interceptors?.response ?? new Interceptors(),
- },
- });
-
- this.default = new DefaultService(this.request);
- }
-}
diff --git a/www/app/api/auth/[...nextauth]/route.ts b/www/app/api/auth/[...nextauth]/route.ts
index 915ed04d..7b73c22a 100644
--- a/www/app/api/auth/[...nextauth]/route.ts
+++ b/www/app/api/auth/[...nextauth]/route.ts
@@ -1,8 +1,5 @@
-// NextAuth route handler for Authentik
-// Refresh rotation has been taken from https://next-auth.js.org/v3/tutorials/refresh-token-rotation even if we are using 4.x
-
import NextAuth from "next-auth";
-import { authOptions } from "../../../lib/auth";
+import { authOptions } from "../../../lib/authBackend";
const handler = NextAuth(authOptions);
diff --git a/www/app/api/core/ApiError.ts b/www/app/api/core/ApiError.ts
deleted file mode 100644
index 1d07bb31..00000000
--- a/www/app/api/core/ApiError.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { ApiRequestOptions } from "./ApiRequestOptions";
-import type { ApiResult } from "./ApiResult";
-
-export class ApiError extends Error {
- public readonly url: string;
- public readonly status: number;
- public readonly statusText: string;
- public readonly body: unknown;
- public readonly request: ApiRequestOptions;
-
- constructor(
- request: ApiRequestOptions,
- response: ApiResult,
- message: string,
- ) {
- super(message);
-
- this.name = "ApiError";
- this.url = response.url;
- this.status = response.status;
- this.statusText = response.statusText;
- this.body = response.body;
- this.request = request;
- }
-}
diff --git a/www/app/api/core/ApiRequestOptions.ts b/www/app/api/core/ApiRequestOptions.ts
deleted file mode 100644
index 57fbb095..00000000
--- a/www/app/api/core/ApiRequestOptions.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export type ApiRequestOptions = {
- readonly method:
- | "GET"
- | "PUT"
- | "POST"
- | "DELETE"
- | "OPTIONS"
- | "HEAD"
- | "PATCH";
- readonly url: string;
- readonly path?: Record;
- readonly cookies?: Record;
- readonly headers?: Record;
- readonly query?: Record;
- readonly formData?: Record;
- readonly body?: any;
- readonly mediaType?: string;
- readonly responseHeader?: string;
- readonly responseTransformer?: (data: unknown) => Promise;
- readonly errors?: Record;
-};
diff --git a/www/app/api/core/ApiResult.ts b/www/app/api/core/ApiResult.ts
deleted file mode 100644
index 05040ba8..00000000
--- a/www/app/api/core/ApiResult.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type ApiResult = {
- readonly body: TData;
- readonly ok: boolean;
- readonly status: number;
- readonly statusText: string;
- readonly url: string;
-};
diff --git a/www/app/api/core/AxiosHttpRequest.ts b/www/app/api/core/AxiosHttpRequest.ts
deleted file mode 100644
index aba5096e..00000000
--- a/www/app/api/core/AxiosHttpRequest.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { ApiRequestOptions } from "./ApiRequestOptions";
-import { BaseHttpRequest } from "./BaseHttpRequest";
-import type { CancelablePromise } from "./CancelablePromise";
-import type { OpenAPIConfig } from "./OpenAPI";
-import { request as __request } from "./request";
-
-export class AxiosHttpRequest extends BaseHttpRequest {
- constructor(config: OpenAPIConfig) {
- super(config);
- }
-
- /**
- * Request method
- * @param options The request options from the service
- * @returns CancelablePromise
- * @throws ApiError
- */
- public override request(
- options: ApiRequestOptions,
- ): CancelablePromise {
- return __request(this.config, options);
- }
-}
diff --git a/www/app/api/core/BaseHttpRequest.ts b/www/app/api/core/BaseHttpRequest.ts
deleted file mode 100644
index 3f89861c..00000000
--- a/www/app/api/core/BaseHttpRequest.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { ApiRequestOptions } from "./ApiRequestOptions";
-import type { CancelablePromise } from "./CancelablePromise";
-import type { OpenAPIConfig } from "./OpenAPI";
-
-export abstract class BaseHttpRequest {
- constructor(public readonly config: OpenAPIConfig) {}
-
- public abstract request(
- options: ApiRequestOptions,
- ): CancelablePromise;
-}
diff --git a/www/app/api/core/CancelablePromise.ts b/www/app/api/core/CancelablePromise.ts
deleted file mode 100644
index 0640e989..00000000
--- a/www/app/api/core/CancelablePromise.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-export class CancelError extends Error {
- constructor(message: string) {
- super(message);
- this.name = "CancelError";
- }
-
- public get isCancelled(): boolean {
- return true;
- }
-}
-
-export interface OnCancel {
- readonly isResolved: boolean;
- readonly isRejected: boolean;
- readonly isCancelled: boolean;
-
- (cancelHandler: () => void): void;
-}
-
-export class CancelablePromise implements Promise {
- private _isResolved: boolean;
- private _isRejected: boolean;
- private _isCancelled: boolean;
- readonly cancelHandlers: (() => void)[];
- readonly promise: Promise;
- private _resolve?: (value: T | PromiseLike) => void;
- private _reject?: (reason?: unknown) => void;
-
- constructor(
- executor: (
- resolve: (value: T | PromiseLike) => void,
- reject: (reason?: unknown) => void,
- onCancel: OnCancel,
- ) => void,
- ) {
- this._isResolved = false;
- this._isRejected = false;
- this._isCancelled = false;
- this.cancelHandlers = [];
- this.promise = new Promise((resolve, reject) => {
- this._resolve = resolve;
- this._reject = reject;
-
- const onResolve = (value: T | PromiseLike): void => {
- if (this._isResolved || this._isRejected || this._isCancelled) {
- return;
- }
- this._isResolved = true;
- if (this._resolve) this._resolve(value);
- };
-
- const onReject = (reason?: unknown): void => {
- if (this._isResolved || this._isRejected || this._isCancelled) {
- return;
- }
- this._isRejected = true;
- if (this._reject) this._reject(reason);
- };
-
- const onCancel = (cancelHandler: () => void): void => {
- if (this._isResolved || this._isRejected || this._isCancelled) {
- return;
- }
- this.cancelHandlers.push(cancelHandler);
- };
-
- Object.defineProperty(onCancel, "isResolved", {
- get: (): boolean => this._isResolved,
- });
-
- Object.defineProperty(onCancel, "isRejected", {
- get: (): boolean => this._isRejected,
- });
-
- Object.defineProperty(onCancel, "isCancelled", {
- get: (): boolean => this._isCancelled,
- });
-
- return executor(onResolve, onReject, onCancel as OnCancel);
- });
- }
-
- get [Symbol.toStringTag]() {
- return "Cancellable Promise";
- }
-
- public then(
- onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null,
- onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null,
- ): Promise {
- return this.promise.then(onFulfilled, onRejected);
- }
-
- public catch(
- onRejected?: ((reason: unknown) => TResult | PromiseLike) | null,
- ): Promise {
- return this.promise.catch(onRejected);
- }
-
- public finally(onFinally?: (() => void) | null): Promise {
- return this.promise.finally(onFinally);
- }
-
- public cancel(): void {
- if (this._isResolved || this._isRejected || this._isCancelled) {
- return;
- }
- this._isCancelled = true;
- if (this.cancelHandlers.length) {
- try {
- for (const cancelHandler of this.cancelHandlers) {
- cancelHandler();
- }
- } catch (error) {
- console.warn("Cancellation threw an error", error);
- return;
- }
- }
- this.cancelHandlers.length = 0;
- if (this._reject) this._reject(new CancelError("Request aborted"));
- }
-
- public get isCancelled(): boolean {
- return this._isCancelled;
- }
-}
diff --git a/www/app/api/core/OpenAPI.ts b/www/app/api/core/OpenAPI.ts
deleted file mode 100644
index 20ea0ed9..00000000
--- a/www/app/api/core/OpenAPI.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import type { AxiosRequestConfig, AxiosResponse } from "axios";
-import type { ApiRequestOptions } from "./ApiRequestOptions";
-
-type Headers = Record;
-type Middleware = (value: T) => T | Promise;
-type Resolver = (options: ApiRequestOptions) => Promise;
-
-export class Interceptors {
- _fns: Middleware[];
-
- constructor() {
- this._fns = [];
- }
-
- eject(fn: Middleware): void {
- const index = this._fns.indexOf(fn);
- if (index !== -1) {
- this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
- }
- }
-
- use(fn: Middleware): void {
- this._fns = [...this._fns, fn];
- }
-}
-
-export type OpenAPIConfig = {
- BASE: string;
- CREDENTIALS: "include" | "omit" | "same-origin";
- ENCODE_PATH?: ((path: string) => string) | undefined;
- HEADERS?: Headers | Resolver | undefined;
- PASSWORD?: string | Resolver | undefined;
- TOKEN?: string | Resolver | undefined;
- USERNAME?: string | Resolver | undefined;
- VERSION: string;
- WITH_CREDENTIALS: boolean;
- interceptors: {
- request: Interceptors;
- response: Interceptors;
- };
-};
-
-export const OpenAPI: OpenAPIConfig = {
- BASE: "",
- CREDENTIALS: "include",
- ENCODE_PATH: undefined,
- HEADERS: undefined,
- PASSWORD: undefined,
- TOKEN: undefined,
- USERNAME: undefined,
- VERSION: "0.1.0",
- WITH_CREDENTIALS: false,
- interceptors: {
- request: new Interceptors(),
- response: new Interceptors(),
- },
-};
diff --git a/www/app/api/core/request.ts b/www/app/api/core/request.ts
deleted file mode 100644
index b576207e..00000000
--- a/www/app/api/core/request.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-import axios from "axios";
-import type {
- AxiosError,
- AxiosRequestConfig,
- AxiosResponse,
- AxiosInstance,
-} from "axios";
-
-import { ApiError } from "./ApiError";
-import type { ApiRequestOptions } from "./ApiRequestOptions";
-import type { ApiResult } from "./ApiResult";
-import { CancelablePromise } from "./CancelablePromise";
-import type { OnCancel } from "./CancelablePromise";
-import type { OpenAPIConfig } from "./OpenAPI";
-
-export const isString = (value: unknown): value is string => {
- return typeof value === "string";
-};
-
-export const isStringWithValue = (value: unknown): value is string => {
- return isString(value) && value !== "";
-};
-
-export const isBlob = (value: any): value is Blob => {
- return value instanceof Blob;
-};
-
-export const isFormData = (value: unknown): value is FormData => {
- return value instanceof FormData;
-};
-
-export const isSuccess = (status: number): boolean => {
- return status >= 200 && status < 300;
-};
-
-export const base64 = (str: string): string => {
- try {
- return btoa(str);
- } catch (err) {
- // @ts-ignore
- return Buffer.from(str).toString("base64");
- }
-};
-
-export const getQueryString = (params: Record): string => {
- const qs: string[] = [];
-
- const append = (key: string, value: unknown) => {
- qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
- };
-
- const encodePair = (key: string, value: unknown) => {
- if (value === undefined || value === null) {
- return;
- }
-
- if (value instanceof Date) {
- append(key, value.toISOString());
- } else if (Array.isArray(value)) {
- value.forEach((v) => encodePair(key, v));
- } else if (typeof value === "object") {
- Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
- } else {
- append(key, value);
- }
- };
-
- Object.entries(params).forEach(([key, value]) => encodePair(key, value));
-
- return qs.length ? `?${qs.join("&")}` : "";
-};
-
-const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
- const encoder = config.ENCODE_PATH || encodeURI;
-
- const path = options.url
- .replace("{api-version}", config.VERSION)
- .replace(/{(.*?)}/g, (substring: string, group: string) => {
- if (options.path?.hasOwnProperty(group)) {
- return encoder(String(options.path[group]));
- }
- return substring;
- });
-
- const url = config.BASE + path;
- return options.query ? url + getQueryString(options.query) : url;
-};
-
-export const getFormData = (
- options: ApiRequestOptions,
-): FormData | undefined => {
- if (options.formData) {
- const formData = new FormData();
-
- const process = (key: string, value: unknown) => {
- if (isString(value) || isBlob(value)) {
- formData.append(key, value);
- } else {
- formData.append(key, JSON.stringify(value));
- }
- };
-
- Object.entries(options.formData)
- .filter(([, value]) => value !== undefined && value !== null)
- .forEach(([key, value]) => {
- if (Array.isArray(value)) {
- value.forEach((v) => process(key, v));
- } else {
- process(key, value);
- }
- });
-
- return formData;
- }
- return undefined;
-};
-
-type Resolver = (options: ApiRequestOptions) => Promise;
-
-export const resolve = async (
- options: ApiRequestOptions,
- resolver?: T | Resolver,
-): Promise => {
- if (typeof resolver === "function") {
- return (resolver as Resolver)(options);
- }
- return resolver;
-};
-
-export const getHeaders = async (
- config: OpenAPIConfig,
- options: ApiRequestOptions,
-): Promise> => {
- const [token, username, password, additionalHeaders] = await Promise.all([
- // @ts-ignore
- resolve(options, config.TOKEN),
- // @ts-ignore
- resolve(options, config.USERNAME),
- // @ts-ignore
- resolve(options, config.PASSWORD),
- // @ts-ignore
- resolve(options, config.HEADERS),
- ]);
-
- const headers = Object.entries({
- Accept: "application/json",
- ...additionalHeaders,
- ...options.headers,
- })
- .filter(([, value]) => value !== undefined && value !== null)
- .reduce(
- (headers, [key, value]) => ({
- ...headers,
- [key]: String(value),
- }),
- {} as Record,
- );
-
- if (isStringWithValue(token)) {
- headers["Authorization"] = `Bearer ${token}`;
- }
-
- if (isStringWithValue(username) && isStringWithValue(password)) {
- const credentials = base64(`${username}:${password}`);
- headers["Authorization"] = `Basic ${credentials}`;
- }
-
- if (options.body !== undefined) {
- if (options.mediaType) {
- headers["Content-Type"] = options.mediaType;
- } else if (isBlob(options.body)) {
- headers["Content-Type"] = options.body.type || "application/octet-stream";
- } else if (isString(options.body)) {
- headers["Content-Type"] = "text/plain";
- } else if (!isFormData(options.body)) {
- headers["Content-Type"] = "application/json";
- }
- } else if (options.formData !== undefined) {
- if (options.mediaType) {
- headers["Content-Type"] = options.mediaType;
- }
- }
-
- return headers;
-};
-
-export const getRequestBody = (options: ApiRequestOptions): unknown => {
- if (options.body) {
- return options.body;
- }
- return undefined;
-};
-
-export const sendRequest = async (
- config: OpenAPIConfig,
- options: ApiRequestOptions,
- url: string,
- body: unknown,
- formData: FormData | undefined,
- headers: Record,
- onCancel: OnCancel,
- axiosClient: AxiosInstance,
-): Promise> => {
- const controller = new AbortController();
-
- let requestConfig: AxiosRequestConfig = {
- data: body ?? formData,
- headers,
- method: options.method,
- signal: controller.signal,
- url,
- withCredentials: config.WITH_CREDENTIALS,
- };
-
- onCancel(() => controller.abort());
-
- for (const fn of config.interceptors.request._fns) {
- requestConfig = await fn(requestConfig);
- }
-
- try {
- return await axiosClient.request(requestConfig);
- } catch (error) {
- const axiosError = error as AxiosError;
- if (axiosError.response) {
- return axiosError.response;
- }
- throw error;
- }
-};
-
-export const getResponseHeader = (
- response: AxiosResponse,
- responseHeader?: string,
-): string | undefined => {
- if (responseHeader) {
- const content = response.headers[responseHeader];
- if (isString(content)) {
- return content;
- }
- }
- return undefined;
-};
-
-export const getResponseBody = (response: AxiosResponse): unknown => {
- if (response.status !== 204) {
- return response.data;
- }
- return undefined;
-};
-
-export const catchErrorCodes = (
- options: ApiRequestOptions,
- result: ApiResult,
-): void => {
- const errors: Record = {
- 400: "Bad Request",
- 401: "Unauthorized",
- 402: "Payment Required",
- 403: "Forbidden",
- 404: "Not Found",
- 405: "Method Not Allowed",
- 406: "Not Acceptable",
- 407: "Proxy Authentication Required",
- 408: "Request Timeout",
- 409: "Conflict",
- 410: "Gone",
- 411: "Length Required",
- 412: "Precondition Failed",
- 413: "Payload Too Large",
- 414: "URI Too Long",
- 415: "Unsupported Media Type",
- 416: "Range Not Satisfiable",
- 417: "Expectation Failed",
- 418: "Im a teapot",
- 421: "Misdirected Request",
- 422: "Unprocessable Content",
- 423: "Locked",
- 424: "Failed Dependency",
- 425: "Too Early",
- 426: "Upgrade Required",
- 428: "Precondition Required",
- 429: "Too Many Requests",
- 431: "Request Header Fields Too Large",
- 451: "Unavailable For Legal Reasons",
- 500: "Internal Server Error",
- 501: "Not Implemented",
- 502: "Bad Gateway",
- 503: "Service Unavailable",
- 504: "Gateway Timeout",
- 505: "HTTP Version Not Supported",
- 506: "Variant Also Negotiates",
- 507: "Insufficient Storage",
- 508: "Loop Detected",
- 510: "Not Extended",
- 511: "Network Authentication Required",
- ...options.errors,
- };
-
- const error = errors[result.status];
- if (error) {
- throw new ApiError(options, result, error);
- }
-
- if (!result.ok) {
- const errorStatus = result.status ?? "unknown";
- const errorStatusText = result.statusText ?? "unknown";
- const errorBody = (() => {
- try {
- return JSON.stringify(result.body, null, 2);
- } catch (e) {
- return undefined;
- }
- })();
-
- throw new ApiError(
- options,
- result,
- `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,
- );
- }
-};
-
-/**
- * Request method
- * @param config The OpenAPI configuration object
- * @param options The request options from the service
- * @param axiosClient The axios client instance to use
- * @returns CancelablePromise
- * @throws ApiError
- */
-export const request = (
- config: OpenAPIConfig,
- options: ApiRequestOptions,
- axiosClient: AxiosInstance = axios,
-): CancelablePromise => {
- return new CancelablePromise(async (resolve, reject, onCancel) => {
- try {
- const url = getUrl(config, options);
- const formData = getFormData(options);
- const body = getRequestBody(options);
- const headers = await getHeaders(config, options);
-
- if (!onCancel.isCancelled) {
- let response = await sendRequest(
- config,
- options,
- url,
- body,
- formData,
- headers,
- onCancel,
- axiosClient,
- );
-
- for (const fn of config.interceptors.response._fns) {
- response = await fn(response);
- }
-
- const responseBody = getResponseBody(response);
- const responseHeader = getResponseHeader(
- response,
- options.responseHeader,
- );
-
- let transformedBody = responseBody;
- if (options.responseTransformer && isSuccess(response.status)) {
- transformedBody = await options.responseTransformer(responseBody);
- }
-
- const result: ApiResult = {
- url,
- ok: isSuccess(response.status),
- status: response.status,
- statusText: response.statusText,
- body: responseHeader ?? transformedBody,
- };
-
- catchErrorCodes(options, result);
-
- resolve(result.body);
- }
- } catch (error) {
- reject(error);
- }
- });
-};
diff --git a/www/app/api/index.ts b/www/app/api/index.ts
deleted file mode 100644
index 27fbb57d..00000000
--- a/www/app/api/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-export { OpenApi } from "./OpenApi";
-export { ApiError } from "./core/ApiError";
-export { BaseHttpRequest } from "./core/BaseHttpRequest";
-export { CancelablePromise, CancelError } from "./core/CancelablePromise";
-export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI";
-export * from "./schemas.gen";
-export * from "./services.gen";
-export * from "./types.gen";
diff --git a/www/app/api/schemas.gen.ts b/www/app/api/schemas.gen.ts
index 03091a5f..e69de29b 100644
--- a/www/app/api/schemas.gen.ts
+++ b/www/app/api/schemas.gen.ts
@@ -1,1776 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-export const $AudioWaveform = {
- properties: {
- data: {
- items: {
- type: "number",
- },
- type: "array",
- title: "Data",
- },
- },
- type: "object",
- required: ["data"],
- title: "AudioWaveform",
-} as const;
-
-export const $Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post =
- {
- properties: {
- chunk: {
- type: "string",
- format: "binary",
- title: "Chunk",
- },
- },
- type: "object",
- required: ["chunk"],
- title:
- "Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post",
- } as const;
-
-export const $CreateParticipant = {
- properties: {
- speaker: {
- anyOf: [
- {
- type: "integer",
- },
- {
- type: "null",
- },
- ],
- title: "Speaker",
- },
- name: {
- type: "string",
- title: "Name",
- },
- },
- type: "object",
- required: ["name"],
- title: "CreateParticipant",
-} as const;
-
-export const $CreateRoom = {
- properties: {
- name: {
- type: "string",
- title: "Name",
- },
- zulip_auto_post: {
- type: "boolean",
- title: "Zulip Auto Post",
- },
- zulip_stream: {
- type: "string",
- title: "Zulip Stream",
- },
- zulip_topic: {
- type: "string",
- title: "Zulip Topic",
- },
- is_locked: {
- type: "boolean",
- title: "Is Locked",
- },
- room_mode: {
- type: "string",
- title: "Room Mode",
- },
- recording_type: {
- type: "string",
- title: "Recording Type",
- },
- recording_trigger: {
- type: "string",
- title: "Recording Trigger",
- },
- is_shared: {
- type: "boolean",
- title: "Is Shared",
- },
- webhook_url: {
- type: "string",
- title: "Webhook Url",
- },
- webhook_secret: {
- type: "string",
- title: "Webhook Secret",
- },
- },
- type: "object",
- required: [
- "name",
- "zulip_auto_post",
- "zulip_stream",
- "zulip_topic",
- "is_locked",
- "room_mode",
- "recording_type",
- "recording_trigger",
- "is_shared",
- "webhook_url",
- "webhook_secret",
- ],
- title: "CreateRoom",
-} as const;
-
-export const $CreateTranscript = {
- properties: {
- name: {
- type: "string",
- title: "Name",
- },
- source_language: {
- type: "string",
- title: "Source Language",
- default: "en",
- },
- target_language: {
- type: "string",
- title: "Target Language",
- default: "en",
- },
- source_kind: {
- anyOf: [
- {
- $ref: "#/components/schemas/SourceKind",
- },
- {
- type: "null",
- },
- ],
- },
- },
- type: "object",
- required: ["name"],
- title: "CreateTranscript",
-} as const;
-
-export const $DeletionStatus = {
- properties: {
- status: {
- type: "string",
- title: "Status",
- },
- },
- type: "object",
- required: ["status"],
- title: "DeletionStatus",
-} as const;
-
-export const $GetTranscript = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- user_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "User Id",
- },
- name: {
- type: "string",
- title: "Name",
- },
- status: {
- type: "string",
- title: "Status",
- },
- locked: {
- type: "boolean",
- title: "Locked",
- },
- duration: {
- type: "number",
- title: "Duration",
- },
- title: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Title",
- },
- short_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Short Summary",
- },
- long_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Long Summary",
- },
- created_at: {
- type: "string",
- title: "Created At",
- },
- share_mode: {
- type: "string",
- title: "Share Mode",
- default: "private",
- },
- source_language: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Source Language",
- },
- target_language: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Target Language",
- },
- reviewed: {
- type: "boolean",
- title: "Reviewed",
- },
- meeting_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Meeting Id",
- },
- source_kind: {
- $ref: "#/components/schemas/SourceKind",
- },
- room_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Id",
- },
- room_name: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Name",
- },
- audio_deleted: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Audio Deleted",
- },
- participants: {
- anyOf: [
- {
- items: {
- $ref: "#/components/schemas/TranscriptParticipant",
- },
- type: "array",
- },
- {
- type: "null",
- },
- ],
- title: "Participants",
- },
- },
- type: "object",
- required: [
- "id",
- "user_id",
- "name",
- "status",
- "locked",
- "duration",
- "title",
- "short_summary",
- "long_summary",
- "created_at",
- "source_language",
- "target_language",
- "reviewed",
- "meeting_id",
- "source_kind",
- "participants",
- ],
- title: "GetTranscript",
-} as const;
-
-export const $GetTranscriptMinimal = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- user_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "User Id",
- },
- name: {
- type: "string",
- title: "Name",
- },
- status: {
- type: "string",
- title: "Status",
- },
- locked: {
- type: "boolean",
- title: "Locked",
- },
- duration: {
- type: "number",
- title: "Duration",
- },
- title: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Title",
- },
- short_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Short Summary",
- },
- long_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Long Summary",
- },
- created_at: {
- type: "string",
- title: "Created At",
- },
- share_mode: {
- type: "string",
- title: "Share Mode",
- default: "private",
- },
- source_language: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Source Language",
- },
- target_language: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Target Language",
- },
- reviewed: {
- type: "boolean",
- title: "Reviewed",
- },
- meeting_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Meeting Id",
- },
- source_kind: {
- $ref: "#/components/schemas/SourceKind",
- },
- room_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Id",
- },
- room_name: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Name",
- },
- audio_deleted: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Audio Deleted",
- },
- },
- type: "object",
- required: [
- "id",
- "user_id",
- "name",
- "status",
- "locked",
- "duration",
- "title",
- "short_summary",
- "long_summary",
- "created_at",
- "source_language",
- "target_language",
- "reviewed",
- "meeting_id",
- "source_kind",
- ],
- title: "GetTranscriptMinimal",
-} as const;
-
-export const $GetTranscriptSegmentTopic = {
- properties: {
- text: {
- type: "string",
- title: "Text",
- },
- start: {
- type: "number",
- title: "Start",
- },
- speaker: {
- type: "integer",
- title: "Speaker",
- },
- },
- type: "object",
- required: ["text", "start", "speaker"],
- title: "GetTranscriptSegmentTopic",
-} as const;
-
-export const $GetTranscriptTopic = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- title: {
- type: "string",
- title: "Title",
- },
- summary: {
- type: "string",
- title: "Summary",
- },
- timestamp: {
- type: "number",
- title: "Timestamp",
- },
- duration: {
- anyOf: [
- {
- type: "number",
- },
- {
- type: "null",
- },
- ],
- title: "Duration",
- },
- transcript: {
- type: "string",
- title: "Transcript",
- },
- segments: {
- items: {
- $ref: "#/components/schemas/GetTranscriptSegmentTopic",
- },
- type: "array",
- title: "Segments",
- default: [],
- },
- },
- type: "object",
- required: ["id", "title", "summary", "timestamp", "duration", "transcript"],
- title: "GetTranscriptTopic",
-} as const;
-
-export const $GetTranscriptTopicWithWords = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- title: {
- type: "string",
- title: "Title",
- },
- summary: {
- type: "string",
- title: "Summary",
- },
- timestamp: {
- type: "number",
- title: "Timestamp",
- },
- duration: {
- anyOf: [
- {
- type: "number",
- },
- {
- type: "null",
- },
- ],
- title: "Duration",
- },
- transcript: {
- type: "string",
- title: "Transcript",
- },
- segments: {
- items: {
- $ref: "#/components/schemas/GetTranscriptSegmentTopic",
- },
- type: "array",
- title: "Segments",
- default: [],
- },
- words: {
- items: {
- $ref: "#/components/schemas/Word",
- },
- type: "array",
- title: "Words",
- default: [],
- },
- },
- type: "object",
- required: ["id", "title", "summary", "timestamp", "duration", "transcript"],
- title: "GetTranscriptTopicWithWords",
-} as const;
-
-export const $GetTranscriptTopicWithWordsPerSpeaker = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- title: {
- type: "string",
- title: "Title",
- },
- summary: {
- type: "string",
- title: "Summary",
- },
- timestamp: {
- type: "number",
- title: "Timestamp",
- },
- duration: {
- anyOf: [
- {
- type: "number",
- },
- {
- type: "null",
- },
- ],
- title: "Duration",
- },
- transcript: {
- type: "string",
- title: "Transcript",
- },
- segments: {
- items: {
- $ref: "#/components/schemas/GetTranscriptSegmentTopic",
- },
- type: "array",
- title: "Segments",
- default: [],
- },
- words_per_speaker: {
- items: {
- $ref: "#/components/schemas/SpeakerWords",
- },
- type: "array",
- title: "Words Per Speaker",
- default: [],
- },
- },
- type: "object",
- required: ["id", "title", "summary", "timestamp", "duration", "transcript"],
- title: "GetTranscriptTopicWithWordsPerSpeaker",
-} as const;
-
-export const $HTTPValidationError = {
- properties: {
- detail: {
- items: {
- $ref: "#/components/schemas/ValidationError",
- },
- type: "array",
- title: "Detail",
- },
- },
- type: "object",
- title: "HTTPValidationError",
-} as const;
-
-export const $Meeting = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- room_name: {
- type: "string",
- title: "Room Name",
- },
- room_url: {
- type: "string",
- title: "Room Url",
- },
- host_room_url: {
- type: "string",
- title: "Host Room Url",
- },
- start_date: {
- type: "string",
- format: "date-time",
- title: "Start Date",
- },
- end_date: {
- type: "string",
- format: "date-time",
- title: "End Date",
- },
- recording_type: {
- type: "string",
- enum: ["none", "local", "cloud"],
- title: "Recording Type",
- default: "cloud",
- },
- },
- type: "object",
- required: [
- "id",
- "room_name",
- "room_url",
- "host_room_url",
- "start_date",
- "end_date",
- ],
- title: "Meeting",
-} as const;
-
-export const $MeetingConsentRequest = {
- properties: {
- consent_given: {
- type: "boolean",
- title: "Consent Given",
- },
- },
- type: "object",
- required: ["consent_given"],
- title: "MeetingConsentRequest",
-} as const;
-
-export const $Page_GetTranscriptMinimal_ = {
- properties: {
- items: {
- items: {
- $ref: "#/components/schemas/GetTranscriptMinimal",
- },
- type: "array",
- title: "Items",
- },
- total: {
- anyOf: [
- {
- type: "integer",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Total",
- },
- page: {
- anyOf: [
- {
- type: "integer",
- minimum: 1,
- },
- {
- type: "null",
- },
- ],
- title: "Page",
- },
- size: {
- anyOf: [
- {
- type: "integer",
- minimum: 1,
- },
- {
- type: "null",
- },
- ],
- title: "Size",
- },
- pages: {
- anyOf: [
- {
- type: "integer",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Pages",
- },
- },
- type: "object",
- required: ["items", "page", "size"],
- title: "Page[GetTranscriptMinimal]",
-} as const;
-
-export const $Page_RoomDetails_ = {
- properties: {
- items: {
- items: {
- $ref: "#/components/schemas/RoomDetails",
- },
- type: "array",
- title: "Items",
- },
- total: {
- anyOf: [
- {
- type: "integer",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Total",
- },
- page: {
- anyOf: [
- {
- type: "integer",
- minimum: 1,
- },
- {
- type: "null",
- },
- ],
- title: "Page",
- },
- size: {
- anyOf: [
- {
- type: "integer",
- minimum: 1,
- },
- {
- type: "null",
- },
- ],
- title: "Size",
- },
- pages: {
- anyOf: [
- {
- type: "integer",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Pages",
- },
- },
- type: "object",
- required: ["items", "page", "size"],
- title: "Page[RoomDetails]",
-} as const;
-
-export const $Participant = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- speaker: {
- anyOf: [
- {
- type: "integer",
- },
- {
- type: "null",
- },
- ],
- title: "Speaker",
- },
- name: {
- type: "string",
- title: "Name",
- },
- },
- type: "object",
- required: ["id", "speaker", "name"],
- title: "Participant",
-} as const;
-
-export const $Room = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- name: {
- type: "string",
- title: "Name",
- },
- user_id: {
- type: "string",
- title: "User Id",
- },
- created_at: {
- type: "string",
- format: "date-time",
- title: "Created At",
- },
- zulip_auto_post: {
- type: "boolean",
- title: "Zulip Auto Post",
- },
- zulip_stream: {
- type: "string",
- title: "Zulip Stream",
- },
- zulip_topic: {
- type: "string",
- title: "Zulip Topic",
- },
- is_locked: {
- type: "boolean",
- title: "Is Locked",
- },
- room_mode: {
- type: "string",
- title: "Room Mode",
- },
- recording_type: {
- type: "string",
- title: "Recording Type",
- },
- recording_trigger: {
- type: "string",
- title: "Recording Trigger",
- },
- is_shared: {
- type: "boolean",
- title: "Is Shared",
- },
- },
- type: "object",
- required: [
- "id",
- "name",
- "user_id",
- "created_at",
- "zulip_auto_post",
- "zulip_stream",
- "zulip_topic",
- "is_locked",
- "room_mode",
- "recording_type",
- "recording_trigger",
- "is_shared",
- ],
- title: "Room",
-} as const;
-
-export const $RoomDetails = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- name: {
- type: "string",
- title: "Name",
- },
- user_id: {
- type: "string",
- title: "User Id",
- },
- created_at: {
- type: "string",
- format: "date-time",
- title: "Created At",
- },
- zulip_auto_post: {
- type: "boolean",
- title: "Zulip Auto Post",
- },
- zulip_stream: {
- type: "string",
- title: "Zulip Stream",
- },
- zulip_topic: {
- type: "string",
- title: "Zulip Topic",
- },
- is_locked: {
- type: "boolean",
- title: "Is Locked",
- },
- room_mode: {
- type: "string",
- title: "Room Mode",
- },
- recording_type: {
- type: "string",
- title: "Recording Type",
- },
- recording_trigger: {
- type: "string",
- title: "Recording Trigger",
- },
- is_shared: {
- type: "boolean",
- title: "Is Shared",
- },
- webhook_url: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Webhook Url",
- },
- webhook_secret: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Webhook Secret",
- },
- },
- type: "object",
- required: [
- "id",
- "name",
- "user_id",
- "created_at",
- "zulip_auto_post",
- "zulip_stream",
- "zulip_topic",
- "is_locked",
- "room_mode",
- "recording_type",
- "recording_trigger",
- "is_shared",
- "webhook_url",
- "webhook_secret",
- ],
- title: "RoomDetails",
-} as const;
-
-export const $RtcOffer = {
- properties: {
- sdp: {
- type: "string",
- title: "Sdp",
- },
- type: {
- type: "string",
- title: "Type",
- },
- },
- type: "object",
- required: ["sdp", "type"],
- title: "RtcOffer",
-} as const;
-
-export const $SearchResponse = {
- properties: {
- results: {
- items: {
- $ref: "#/components/schemas/SearchResult",
- },
- type: "array",
- title: "Results",
- },
- total: {
- type: "integer",
- minimum: 0,
- title: "Total",
- description: "Total number of search results",
- },
- query: {
- anyOf: [
- {
- type: "string",
- minLength: 1,
- description: "Search query text",
- },
- {
- type: "null",
- },
- ],
- title: "Query",
- },
- limit: {
- type: "integer",
- maximum: 100,
- minimum: 1,
- title: "Limit",
- description: "Results per page",
- },
- offset: {
- type: "integer",
- minimum: 0,
- title: "Offset",
- description: "Number of results to skip",
- },
- },
- type: "object",
- required: ["results", "total", "limit", "offset"],
- title: "SearchResponse",
-} as const;
-
-export const $SearchResult = {
- properties: {
- id: {
- type: "string",
- minLength: 1,
- title: "Id",
- },
- title: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Title",
- },
- user_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "User Id",
- },
- room_id: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Id",
- },
- room_name: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Room Name",
- },
- source_kind: {
- $ref: "#/components/schemas/SourceKind",
- },
- created_at: {
- type: "string",
- title: "Created At",
- },
- status: {
- type: "string",
- minLength: 1,
- title: "Status",
- },
- rank: {
- type: "number",
- maximum: 1,
- minimum: 0,
- title: "Rank",
- },
- duration: {
- anyOf: [
- {
- type: "number",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Duration",
- description: "Duration in seconds",
- },
- search_snippets: {
- items: {
- type: "string",
- },
- type: "array",
- title: "Search Snippets",
- description: "Text snippets around search matches",
- },
- total_match_count: {
- type: "integer",
- minimum: 0,
- title: "Total Match Count",
- description: "Total number of matches found in the transcript",
- default: 0,
- },
- },
- type: "object",
- required: [
- "id",
- "source_kind",
- "created_at",
- "status",
- "rank",
- "duration",
- "search_snippets",
- ],
- title: "SearchResult",
- description: "Public search result model with computed fields.",
-} as const;
-
-export const $SourceKind = {
- type: "string",
- enum: ["room", "live", "file"],
- title: "SourceKind",
-} as const;
-
-export const $SpeakerAssignment = {
- properties: {
- speaker: {
- anyOf: [
- {
- type: "integer",
- minimum: 0,
- },
- {
- type: "null",
- },
- ],
- title: "Speaker",
- },
- participant: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Participant",
- },
- timestamp_from: {
- type: "number",
- title: "Timestamp From",
- },
- timestamp_to: {
- type: "number",
- title: "Timestamp To",
- },
- },
- type: "object",
- required: ["timestamp_from", "timestamp_to"],
- title: "SpeakerAssignment",
-} as const;
-
-export const $SpeakerAssignmentStatus = {
- properties: {
- status: {
- type: "string",
- title: "Status",
- },
- },
- type: "object",
- required: ["status"],
- title: "SpeakerAssignmentStatus",
-} as const;
-
-export const $SpeakerMerge = {
- properties: {
- speaker_from: {
- type: "integer",
- title: "Speaker From",
- },
- speaker_to: {
- type: "integer",
- title: "Speaker To",
- },
- },
- type: "object",
- required: ["speaker_from", "speaker_to"],
- title: "SpeakerMerge",
-} as const;
-
-export const $SpeakerWords = {
- properties: {
- speaker: {
- type: "integer",
- title: "Speaker",
- },
- words: {
- items: {
- $ref: "#/components/schemas/Word",
- },
- type: "array",
- title: "Words",
- },
- },
- type: "object",
- required: ["speaker", "words"],
- title: "SpeakerWords",
-} as const;
-
-export const $Stream = {
- properties: {
- stream_id: {
- type: "integer",
- title: "Stream Id",
- },
- name: {
- type: "string",
- title: "Name",
- },
- },
- type: "object",
- required: ["stream_id", "name"],
- title: "Stream",
-} as const;
-
-export const $Topic = {
- properties: {
- name: {
- type: "string",
- title: "Name",
- },
- },
- type: "object",
- required: ["name"],
- title: "Topic",
-} as const;
-
-export const $TranscriptParticipant = {
- properties: {
- id: {
- type: "string",
- title: "Id",
- },
- speaker: {
- anyOf: [
- {
- type: "integer",
- },
- {
- type: "null",
- },
- ],
- title: "Speaker",
- },
- name: {
- type: "string",
- title: "Name",
- },
- },
- type: "object",
- required: ["speaker", "name"],
- title: "TranscriptParticipant",
-} as const;
-
-export const $UpdateParticipant = {
- properties: {
- speaker: {
- anyOf: [
- {
- type: "integer",
- },
- {
- type: "null",
- },
- ],
- title: "Speaker",
- },
- name: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Name",
- },
- },
- type: "object",
- title: "UpdateParticipant",
-} as const;
-
-export const $UpdateRoom = {
- properties: {
- name: {
- type: "string",
- title: "Name",
- },
- zulip_auto_post: {
- type: "boolean",
- title: "Zulip Auto Post",
- },
- zulip_stream: {
- type: "string",
- title: "Zulip Stream",
- },
- zulip_topic: {
- type: "string",
- title: "Zulip Topic",
- },
- is_locked: {
- type: "boolean",
- title: "Is Locked",
- },
- room_mode: {
- type: "string",
- title: "Room Mode",
- },
- recording_type: {
- type: "string",
- title: "Recording Type",
- },
- recording_trigger: {
- type: "string",
- title: "Recording Trigger",
- },
- is_shared: {
- type: "boolean",
- title: "Is Shared",
- },
- webhook_url: {
- type: "string",
- title: "Webhook Url",
- },
- webhook_secret: {
- type: "string",
- title: "Webhook Secret",
- },
- },
- type: "object",
- required: [
- "name",
- "zulip_auto_post",
- "zulip_stream",
- "zulip_topic",
- "is_locked",
- "room_mode",
- "recording_type",
- "recording_trigger",
- "is_shared",
- "webhook_url",
- "webhook_secret",
- ],
- title: "UpdateRoom",
-} as const;
-
-export const $UpdateTranscript = {
- properties: {
- name: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Name",
- },
- locked: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Locked",
- },
- title: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Title",
- },
- short_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Short Summary",
- },
- long_summary: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Long Summary",
- },
- share_mode: {
- anyOf: [
- {
- type: "string",
- enum: ["public", "semi-private", "private"],
- },
- {
- type: "null",
- },
- ],
- title: "Share Mode",
- },
- participants: {
- anyOf: [
- {
- items: {
- $ref: "#/components/schemas/TranscriptParticipant",
- },
- type: "array",
- },
- {
- type: "null",
- },
- ],
- title: "Participants",
- },
- reviewed: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Reviewed",
- },
- audio_deleted: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Audio Deleted",
- },
- },
- type: "object",
- title: "UpdateTranscript",
-} as const;
-
-export const $UserInfo = {
- properties: {
- sub: {
- type: "string",
- title: "Sub",
- },
- email: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Email",
- },
- email_verified: {
- anyOf: [
- {
- type: "boolean",
- },
- {
- type: "null",
- },
- ],
- title: "Email Verified",
- },
- },
- type: "object",
- required: ["sub", "email", "email_verified"],
- title: "UserInfo",
-} as const;
-
-export const $ValidationError = {
- properties: {
- loc: {
- items: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "integer",
- },
- ],
- },
- type: "array",
- title: "Location",
- },
- msg: {
- type: "string",
- title: "Message",
- },
- type: {
- type: "string",
- title: "Error Type",
- },
- },
- type: "object",
- required: ["loc", "msg", "type"],
- title: "ValidationError",
-} as const;
-
-export const $WebhookTestResult = {
- properties: {
- success: {
- type: "boolean",
- title: "Success",
- },
- message: {
- type: "string",
- title: "Message",
- default: "",
- },
- error: {
- type: "string",
- title: "Error",
- default: "",
- },
- status_code: {
- anyOf: [
- {
- type: "integer",
- },
- {
- type: "null",
- },
- ],
- title: "Status Code",
- },
- response_preview: {
- anyOf: [
- {
- type: "string",
- },
- {
- type: "null",
- },
- ],
- title: "Response Preview",
- },
- },
- type: "object",
- required: ["success"],
- title: "WebhookTestResult",
-} as const;
-
-export const $WherebyWebhookEvent = {
- properties: {
- apiVersion: {
- type: "string",
- title: "Apiversion",
- },
- id: {
- type: "string",
- title: "Id",
- },
- createdAt: {
- type: "string",
- format: "date-time",
- title: "Createdat",
- },
- type: {
- type: "string",
- title: "Type",
- },
- data: {
- additionalProperties: true,
- type: "object",
- title: "Data",
- },
- },
- type: "object",
- required: ["apiVersion", "id", "createdAt", "type", "data"],
- title: "WherebyWebhookEvent",
-} as const;
-
-export const $Word = {
- properties: {
- text: {
- type: "string",
- title: "Text",
- },
- start: {
- type: "number",
- minimum: 0,
- title: "Start",
- description: "Time in seconds with float part",
- },
- end: {
- type: "number",
- minimum: 0,
- title: "End",
- description: "Time in seconds with float part",
- },
- speaker: {
- type: "integer",
- title: "Speaker",
- default: 0,
- },
- },
- type: "object",
- required: ["text", "start", "end"],
- title: "Word",
-} as const;
diff --git a/www/app/api/services.gen.ts b/www/app/api/services.gen.ts
index c9e027fb..e69de29b 100644
--- a/www/app/api/services.gen.ts
+++ b/www/app/api/services.gen.ts
@@ -1,942 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-import type { CancelablePromise } from "./core/CancelablePromise";
-import type { BaseHttpRequest } from "./core/BaseHttpRequest";
-import type {
- MetricsResponse,
- V1MeetingAudioConsentData,
- V1MeetingAudioConsentResponse,
- V1RoomsListData,
- V1RoomsListResponse,
- V1RoomsCreateData,
- V1RoomsCreateResponse,
- V1RoomsGetData,
- V1RoomsGetResponse,
- V1RoomsUpdateData,
- V1RoomsUpdateResponse,
- V1RoomsDeleteData,
- V1RoomsDeleteResponse,
- V1RoomsCreateMeetingData,
- V1RoomsCreateMeetingResponse,
- V1RoomsTestWebhookData,
- V1RoomsTestWebhookResponse,
- V1TranscriptsListData,
- V1TranscriptsListResponse,
- V1TranscriptsCreateData,
- V1TranscriptsCreateResponse,
- V1TranscriptsSearchData,
- V1TranscriptsSearchResponse,
- V1TranscriptGetData,
- V1TranscriptGetResponse,
- V1TranscriptUpdateData,
- V1TranscriptUpdateResponse,
- V1TranscriptDeleteData,
- V1TranscriptDeleteResponse,
- V1TranscriptGetTopicsData,
- V1TranscriptGetTopicsResponse,
- V1TranscriptGetTopicsWithWordsData,
- V1TranscriptGetTopicsWithWordsResponse,
- V1TranscriptGetTopicsWithWordsPerSpeakerData,
- V1TranscriptGetTopicsWithWordsPerSpeakerResponse,
- V1TranscriptPostToZulipData,
- V1TranscriptPostToZulipResponse,
- V1TranscriptHeadAudioMp3Data,
- V1TranscriptHeadAudioMp3Response,
- V1TranscriptGetAudioMp3Data,
- V1TranscriptGetAudioMp3Response,
- V1TranscriptGetAudioWaveformData,
- V1TranscriptGetAudioWaveformResponse,
- V1TranscriptGetParticipantsData,
- V1TranscriptGetParticipantsResponse,
- V1TranscriptAddParticipantData,
- V1TranscriptAddParticipantResponse,
- V1TranscriptGetParticipantData,
- V1TranscriptGetParticipantResponse,
- V1TranscriptUpdateParticipantData,
- V1TranscriptUpdateParticipantResponse,
- V1TranscriptDeleteParticipantData,
- V1TranscriptDeleteParticipantResponse,
- V1TranscriptAssignSpeakerData,
- V1TranscriptAssignSpeakerResponse,
- V1TranscriptMergeSpeakerData,
- V1TranscriptMergeSpeakerResponse,
- V1TranscriptRecordUploadData,
- V1TranscriptRecordUploadResponse,
- V1TranscriptGetWebsocketEventsData,
- V1TranscriptGetWebsocketEventsResponse,
- V1TranscriptRecordWebrtcData,
- V1TranscriptRecordWebrtcResponse,
- V1TranscriptProcessData,
- V1TranscriptProcessResponse,
- V1UserMeResponse,
- V1ZulipGetStreamsResponse,
- V1ZulipGetTopicsData,
- V1ZulipGetTopicsResponse,
- V1WherebyWebhookData,
- V1WherebyWebhookResponse,
-} from "./types.gen";
-
-export class DefaultService {
- constructor(public readonly httpRequest: BaseHttpRequest) {}
-
- /**
- * Metrics
- * Endpoint that serves Prometheus metrics.
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public metrics(): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/metrics",
- });
- }
-
- /**
- * Meeting Audio Consent
- * @param data The data for the request.
- * @param data.meetingId
- * @param data.requestBody
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1MeetingAudioConsent(
- data: V1MeetingAudioConsentData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/meetings/{meeting_id}/consent",
- path: {
- meeting_id: data.meetingId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms List
- * @param data The data for the request.
- * @param data.page Page number
- * @param data.size Page size
- * @returns Page_RoomDetails_ Successful Response
- * @throws ApiError
- */
- public v1RoomsList(
- data: V1RoomsListData = {},
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/rooms",
- query: {
- page: data.page,
- size: data.size,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Create
- * @param data The data for the request.
- * @param data.requestBody
- * @returns Room Successful Response
- * @throws ApiError
- */
- public v1RoomsCreate(
- data: V1RoomsCreateData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/rooms",
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Get
- * @param data The data for the request.
- * @param data.roomId
- * @returns RoomDetails Successful Response
- * @throws ApiError
- */
- public v1RoomsGet(
- data: V1RoomsGetData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/rooms/{room_id}",
- path: {
- room_id: data.roomId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Update
- * @param data The data for the request.
- * @param data.roomId
- * @param data.requestBody
- * @returns RoomDetails Successful Response
- * @throws ApiError
- */
- public v1RoomsUpdate(
- data: V1RoomsUpdateData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "PATCH",
- url: "/v1/rooms/{room_id}",
- path: {
- room_id: data.roomId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Delete
- * @param data The data for the request.
- * @param data.roomId
- * @returns DeletionStatus Successful Response
- * @throws ApiError
- */
- public v1RoomsDelete(
- data: V1RoomsDeleteData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "DELETE",
- url: "/v1/rooms/{room_id}",
- path: {
- room_id: data.roomId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Create Meeting
- * @param data The data for the request.
- * @param data.roomName
- * @returns Meeting Successful Response
- * @throws ApiError
- */
- public v1RoomsCreateMeeting(
- data: V1RoomsCreateMeetingData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/rooms/{room_name}/meeting",
- path: {
- room_name: data.roomName,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Rooms Test Webhook
- * Test webhook configuration by sending a sample payload.
- * @param data The data for the request.
- * @param data.roomId
- * @returns WebhookTestResult Successful Response
- * @throws ApiError
- */
- public v1RoomsTestWebhook(
- data: V1RoomsTestWebhookData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/rooms/{room_id}/webhook/test",
- path: {
- room_id: data.roomId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcripts List
- * @param data The data for the request.
- * @param data.sourceKind
- * @param data.roomId
- * @param data.searchTerm
- * @param data.page Page number
- * @param data.size Page size
- * @returns Page_GetTranscriptMinimal_ Successful Response
- * @throws ApiError
- */
- public v1TranscriptsList(
- data: V1TranscriptsListData = {},
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts",
- query: {
- source_kind: data.sourceKind,
- room_id: data.roomId,
- search_term: data.searchTerm,
- page: data.page,
- size: data.size,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcripts Create
- * @param data The data for the request.
- * @param data.requestBody
- * @returns GetTranscript Successful Response
- * @throws ApiError
- */
- public v1TranscriptsCreate(
- data: V1TranscriptsCreateData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts",
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcripts Search
- * Full-text search across transcript titles and content.
- * @param data The data for the request.
- * @param data.q Search query text
- * @param data.limit Results per page
- * @param data.offset Number of results to skip
- * @param data.roomId
- * @param data.sourceKind
- * @returns SearchResponse Successful Response
- * @throws ApiError
- */
- public v1TranscriptsSearch(
- data: V1TranscriptsSearchData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/search",
- query: {
- q: data.q,
- limit: data.limit,
- offset: data.offset,
- room_id: data.roomId,
- source_kind: data.sourceKind,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns GetTranscript Successful Response
- * @throws ApiError
- */
- public v1TranscriptGet(
- data: V1TranscriptGetData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Update
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.requestBody
- * @returns GetTranscript Successful Response
- * @throws ApiError
- */
- public v1TranscriptUpdate(
- data: V1TranscriptUpdateData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "PATCH",
- url: "/v1/transcripts/{transcript_id}",
- path: {
- transcript_id: data.transcriptId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Delete
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns DeletionStatus Successful Response
- * @throws ApiError
- */
- public v1TranscriptDelete(
- data: V1TranscriptDeleteData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "DELETE",
- url: "/v1/transcripts/{transcript_id}",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Topics
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns GetTranscriptTopic Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetTopics(
- data: V1TranscriptGetTopicsData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/topics",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Topics With Words
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns GetTranscriptTopicWithWords Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetTopicsWithWords(
- data: V1TranscriptGetTopicsWithWordsData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/topics/with-words",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Topics With Words Per Speaker
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.topicId
- * @returns GetTranscriptTopicWithWordsPerSpeaker Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetTopicsWithWordsPerSpeaker(
- data: V1TranscriptGetTopicsWithWordsPerSpeakerData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker",
- path: {
- transcript_id: data.transcriptId,
- topic_id: data.topicId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Post To Zulip
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.stream
- * @param data.topic
- * @param data.includeTopics
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptPostToZulip(
- data: V1TranscriptPostToZulipData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts/{transcript_id}/zulip",
- path: {
- transcript_id: data.transcriptId,
- },
- query: {
- stream: data.stream,
- topic: data.topic,
- include_topics: data.includeTopics,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Audio Mp3
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.token
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptHeadAudioMp3(
- data: V1TranscriptHeadAudioMp3Data,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "HEAD",
- url: "/v1/transcripts/{transcript_id}/audio/mp3",
- path: {
- transcript_id: data.transcriptId,
- },
- query: {
- token: data.token,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Audio Mp3
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.token
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetAudioMp3(
- data: V1TranscriptGetAudioMp3Data,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/audio/mp3",
- path: {
- transcript_id: data.transcriptId,
- },
- query: {
- token: data.token,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Audio Waveform
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns AudioWaveform Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetAudioWaveform(
- data: V1TranscriptGetAudioWaveformData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/audio/waveform",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Participants
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns Participant Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetParticipants(
- data: V1TranscriptGetParticipantsData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/participants",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Add Participant
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.requestBody
- * @returns Participant Successful Response
- * @throws ApiError
- */
- public v1TranscriptAddParticipant(
- data: V1TranscriptAddParticipantData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts/{transcript_id}/participants",
- path: {
- transcript_id: data.transcriptId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Participant
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.participantId
- * @returns Participant Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetParticipant(
- data: V1TranscriptGetParticipantData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/participants/{participant_id}",
- path: {
- transcript_id: data.transcriptId,
- participant_id: data.participantId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Update Participant
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.participantId
- * @param data.requestBody
- * @returns Participant Successful Response
- * @throws ApiError
- */
- public v1TranscriptUpdateParticipant(
- data: V1TranscriptUpdateParticipantData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "PATCH",
- url: "/v1/transcripts/{transcript_id}/participants/{participant_id}",
- path: {
- transcript_id: data.transcriptId,
- participant_id: data.participantId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Delete Participant
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.participantId
- * @returns DeletionStatus Successful Response
- * @throws ApiError
- */
- public v1TranscriptDeleteParticipant(
- data: V1TranscriptDeleteParticipantData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "DELETE",
- url: "/v1/transcripts/{transcript_id}/participants/{participant_id}",
- path: {
- transcript_id: data.transcriptId,
- participant_id: data.participantId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Assign Speaker
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.requestBody
- * @returns SpeakerAssignmentStatus Successful Response
- * @throws ApiError
- */
- public v1TranscriptAssignSpeaker(
- data: V1TranscriptAssignSpeakerData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "PATCH",
- url: "/v1/transcripts/{transcript_id}/speaker/assign",
- path: {
- transcript_id: data.transcriptId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Merge Speaker
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.requestBody
- * @returns SpeakerAssignmentStatus Successful Response
- * @throws ApiError
- */
- public v1TranscriptMergeSpeaker(
- data: V1TranscriptMergeSpeakerData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "PATCH",
- url: "/v1/transcripts/{transcript_id}/speaker/merge",
- path: {
- transcript_id: data.transcriptId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Record Upload
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.chunkNumber
- * @param data.totalChunks
- * @param data.formData
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptRecordUpload(
- data: V1TranscriptRecordUploadData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts/{transcript_id}/record/upload",
- path: {
- transcript_id: data.transcriptId,
- },
- query: {
- chunk_number: data.chunkNumber,
- total_chunks: data.totalChunks,
- },
- formData: data.formData,
- mediaType: "multipart/form-data",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Get Websocket Events
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptGetWebsocketEvents(
- data: V1TranscriptGetWebsocketEventsData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/transcripts/{transcript_id}/events",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Record Webrtc
- * @param data The data for the request.
- * @param data.transcriptId
- * @param data.requestBody
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptRecordWebrtc(
- data: V1TranscriptRecordWebrtcData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts/{transcript_id}/record/webrtc",
- path: {
- transcript_id: data.transcriptId,
- },
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Transcript Process
- * @param data The data for the request.
- * @param data.transcriptId
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1TranscriptProcess(
- data: V1TranscriptProcessData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/transcripts/{transcript_id}/process",
- path: {
- transcript_id: data.transcriptId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * User Me
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1UserMe(): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/me",
- });
- }
-
- /**
- * Zulip Get Streams
- * Get all Zulip streams.
- * @returns Stream Successful Response
- * @throws ApiError
- */
- public v1ZulipGetStreams(): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/zulip/streams",
- });
- }
-
- /**
- * Zulip Get Topics
- * Get all topics for a specific Zulip stream.
- * @param data The data for the request.
- * @param data.streamId
- * @returns Topic Successful Response
- * @throws ApiError
- */
- public v1ZulipGetTopics(
- data: V1ZulipGetTopicsData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "GET",
- url: "/v1/zulip/streams/{stream_id}/topics",
- path: {
- stream_id: data.streamId,
- },
- errors: {
- 422: "Validation Error",
- },
- });
- }
-
- /**
- * Whereby Webhook
- * @param data The data for the request.
- * @param data.requestBody
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public v1WherebyWebhook(
- data: V1WherebyWebhookData,
- ): CancelablePromise {
- return this.httpRequest.request({
- method: "POST",
- url: "/v1/whereby",
- body: data.requestBody,
- mediaType: "application/json",
- errors: {
- 422: "Validation Error",
- },
- });
- }
-}
diff --git a/www/app/api/types.gen.ts b/www/app/api/types.gen.ts
index d724fc98..e69de29b 100644
--- a/www/app/api/types.gen.ts
+++ b/www/app/api/types.gen.ts
@@ -1,1143 +0,0 @@
-// This file is auto-generated by @hey-api/openapi-ts
-
-export type AudioWaveform = {
- data: Array;
-};
-
-export type Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post =
- {
- chunk: Blob | File;
- };
-
-export type CreateParticipant = {
- speaker?: number | null;
- name: string;
-};
-
-export type CreateRoom = {
- name: string;
- zulip_auto_post: boolean;
- zulip_stream: string;
- zulip_topic: string;
- is_locked: boolean;
- room_mode: string;
- recording_type: string;
- recording_trigger: string;
- is_shared: boolean;
- webhook_url: string;
- webhook_secret: string;
-};
-
-export type CreateTranscript = {
- name: string;
- source_language?: string;
- target_language?: string;
- source_kind?: SourceKind | null;
-};
-
-export type DeletionStatus = {
- status: string;
-};
-
-export type GetTranscript = {
- id: string;
- user_id: string | null;
- name: string;
- status: string;
- locked: boolean;
- duration: number;
- title: string | null;
- short_summary: string | null;
- long_summary: string | null;
- created_at: string;
- share_mode?: string;
- source_language: string | null;
- target_language: string | null;
- reviewed: boolean;
- meeting_id: string | null;
- source_kind: SourceKind;
- room_id?: string | null;
- room_name?: string | null;
- audio_deleted?: boolean | null;
- participants: Array | null;
-};
-
-export type GetTranscriptMinimal = {
- id: string;
- user_id: string | null;
- name: string;
- status: string;
- locked: boolean;
- duration: number;
- title: string | null;
- short_summary: string | null;
- long_summary: string | null;
- created_at: string;
- share_mode?: string;
- source_language: string | null;
- target_language: string | null;
- reviewed: boolean;
- meeting_id: string | null;
- source_kind: SourceKind;
- room_id?: string | null;
- room_name?: string | null;
- audio_deleted?: boolean | null;
-};
-
-export type GetTranscriptSegmentTopic = {
- text: string;
- start: number;
- speaker: number;
-};
-
-export type GetTranscriptTopic = {
- id: string;
- title: string;
- summary: string;
- timestamp: number;
- duration: number | null;
- transcript: string;
- segments?: Array;
-};
-
-export type GetTranscriptTopicWithWords = {
- id: string;
- title: string;
- summary: string;
- timestamp: number;
- duration: number | null;
- transcript: string;
- segments?: Array;
- words?: Array;
-};
-
-export type GetTranscriptTopicWithWordsPerSpeaker = {
- id: string;
- title: string;
- summary: string;
- timestamp: number;
- duration: number | null;
- transcript: string;
- segments?: Array;
- words_per_speaker?: Array;
-};
-
-export type HTTPValidationError = {
- detail?: Array;
-};
-
-export type Meeting = {
- id: string;
- room_name: string;
- room_url: string;
- host_room_url: string;
- start_date: string;
- end_date: string;
- recording_type?: "none" | "local" | "cloud";
-};
-
-export type recording_type = "none" | "local" | "cloud";
-
-export type MeetingConsentRequest = {
- consent_given: boolean;
-};
-
-export type Page_GetTranscriptMinimal_ = {
- items: Array;
- total?: number | null;
- page: number | null;
- size: number | null;
- pages?: number | null;
-};
-
-export type Page_RoomDetails_ = {
- items: Array;
- total?: number | null;
- page: number | null;
- size: number | null;
- pages?: number | null;
-};
-
-export type Participant = {
- id: string;
- speaker: number | null;
- name: string;
-};
-
-export type Room = {
- id: string;
- name: string;
- user_id: string;
- created_at: string;
- zulip_auto_post: boolean;
- zulip_stream: string;
- zulip_topic: string;
- is_locked: boolean;
- room_mode: string;
- recording_type: string;
- recording_trigger: string;
- is_shared: boolean;
-};
-
-export type RoomDetails = {
- id: string;
- name: string;
- user_id: string;
- created_at: string;
- zulip_auto_post: boolean;
- zulip_stream: string;
- zulip_topic: string;
- is_locked: boolean;
- room_mode: string;
- recording_type: string;
- recording_trigger: string;
- is_shared: boolean;
- webhook_url: string | null;
- webhook_secret: string | null;
-};
-
-export type RtcOffer = {
- sdp: string;
- type: string;
-};
-
-export type SearchResponse = {
- results: Array;
- /**
- * Total number of search results
- */
- total: number;
- query?: string | null;
- /**
- * Results per page
- */
- limit: number;
- /**
- * Number of results to skip
- */
- offset: number;
-};
-
-/**
- * Public search result model with computed fields.
- */
-export type SearchResult = {
- id: string;
- title?: string | null;
- user_id?: string | null;
- room_id?: string | null;
- room_name?: string | null;
- source_kind: SourceKind;
- created_at: string;
- status: string;
- rank: number;
- /**
- * Duration in seconds
- */
- duration: number | null;
- /**
- * Text snippets around search matches
- */
- search_snippets: Array;
- /**
- * Total number of matches found in the transcript
- */
- total_match_count?: number;
-};
-
-export type SourceKind = "room" | "live" | "file";
-
-export type SpeakerAssignment = {
- speaker?: number | null;
- participant?: string | null;
- timestamp_from: number;
- timestamp_to: number;
-};
-
-export type SpeakerAssignmentStatus = {
- status: string;
-};
-
-export type SpeakerMerge = {
- speaker_from: number;
- speaker_to: number;
-};
-
-export type SpeakerWords = {
- speaker: number;
- words: Array;
-};
-
-export type Stream = {
- stream_id: number;
- name: string;
-};
-
-export type Topic = {
- name: string;
-};
-
-export type TranscriptParticipant = {
- id?: string;
- speaker: number | null;
- name: string;
-};
-
-export type UpdateParticipant = {
- speaker?: number | null;
- name?: string | null;
-};
-
-export type UpdateRoom = {
- name: string;
- zulip_auto_post: boolean;
- zulip_stream: string;
- zulip_topic: string;
- is_locked: boolean;
- room_mode: string;
- recording_type: string;
- recording_trigger: string;
- is_shared: boolean;
- webhook_url: string;
- webhook_secret: string;
-};
-
-export type UpdateTranscript = {
- name?: string | null;
- locked?: boolean | null;
- title?: string | null;
- short_summary?: string | null;
- long_summary?: string | null;
- share_mode?: "public" | "semi-private" | "private" | null;
- participants?: Array | null;
- reviewed?: boolean | null;
- audio_deleted?: boolean | null;
-};
-
-export type UserInfo = {
- sub: string;
- email: string | null;
- email_verified: boolean | null;
-};
-
-export type ValidationError = {
- loc: Array;
- msg: string;
- type: string;
-};
-
-export type WebhookTestResult = {
- success: boolean;
- message?: string;
- error?: string;
- status_code?: number | null;
- response_preview?: string | null;
-};
-
-export type WherebyWebhookEvent = {
- apiVersion: string;
- id: string;
- createdAt: string;
- type: string;
- data: {
- [key: string]: unknown;
- };
-};
-
-export type Word = {
- text: string;
- /**
- * Time in seconds with float part
- */
- start: number;
- /**
- * Time in seconds with float part
- */
- end: number;
- speaker?: number;
-};
-
-export type MetricsResponse = unknown;
-
-export type V1MeetingAudioConsentData = {
- meetingId: string;
- requestBody: MeetingConsentRequest;
-};
-
-export type V1MeetingAudioConsentResponse = unknown;
-
-export type V1RoomsListData = {
- /**
- * Page number
- */
- page?: number;
- /**
- * Page size
- */
- size?: number;
-};
-
-export type V1RoomsListResponse = Page_RoomDetails_;
-
-export type V1RoomsCreateData = {
- requestBody: CreateRoom;
-};
-
-export type V1RoomsCreateResponse = Room;
-
-export type V1RoomsGetData = {
- roomId: string;
-};
-
-export type V1RoomsGetResponse = RoomDetails;
-
-export type V1RoomsUpdateData = {
- requestBody: UpdateRoom;
- roomId: string;
-};
-
-export type V1RoomsUpdateResponse = RoomDetails;
-
-export type V1RoomsDeleteData = {
- roomId: string;
-};
-
-export type V1RoomsDeleteResponse = DeletionStatus;
-
-export type V1RoomsCreateMeetingData = {
- roomName: string;
-};
-
-export type V1RoomsCreateMeetingResponse = Meeting;
-
-export type V1RoomsTestWebhookData = {
- roomId: string;
-};
-
-export type V1RoomsTestWebhookResponse = WebhookTestResult;
-
-export type V1TranscriptsListData = {
- /**
- * Page number
- */
- page?: number;
- roomId?: string | null;
- searchTerm?: string | null;
- /**
- * Page size
- */
- size?: number;
- sourceKind?: SourceKind | null;
-};
-
-export type V1TranscriptsListResponse = Page_GetTranscriptMinimal_;
-
-export type V1TranscriptsCreateData = {
- requestBody: CreateTranscript;
-};
-
-export type V1TranscriptsCreateResponse = GetTranscript;
-
-export type V1TranscriptsSearchData = {
- /**
- * Results per page
- */
- limit?: number;
- /**
- * Number of results to skip
- */
- offset?: number;
- /**
- * Search query text
- */
- q: string;
- roomId?: string | null;
- sourceKind?: SourceKind | null;
-};
-
-export type V1TranscriptsSearchResponse = SearchResponse;
-
-export type V1TranscriptGetData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetResponse = GetTranscript;
-
-export type V1TranscriptUpdateData = {
- requestBody: UpdateTranscript;
- transcriptId: string;
-};
-
-export type V1TranscriptUpdateResponse = GetTranscript;
-
-export type V1TranscriptDeleteData = {
- transcriptId: string;
-};
-
-export type V1TranscriptDeleteResponse = DeletionStatus;
-
-export type V1TranscriptGetTopicsData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetTopicsResponse = Array;
-
-export type V1TranscriptGetTopicsWithWordsData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetTopicsWithWordsResponse =
- Array;
-
-export type V1TranscriptGetTopicsWithWordsPerSpeakerData = {
- topicId: string;
- transcriptId: string;
-};
-
-export type V1TranscriptGetTopicsWithWordsPerSpeakerResponse =
- GetTranscriptTopicWithWordsPerSpeaker;
-
-export type V1TranscriptPostToZulipData = {
- includeTopics: boolean;
- stream: string;
- topic: string;
- transcriptId: string;
-};
-
-export type V1TranscriptPostToZulipResponse = unknown;
-
-export type V1TranscriptHeadAudioMp3Data = {
- token?: string | null;
- transcriptId: string;
-};
-
-export type V1TranscriptHeadAudioMp3Response = unknown;
-
-export type V1TranscriptGetAudioMp3Data = {
- token?: string | null;
- transcriptId: string;
-};
-
-export type V1TranscriptGetAudioMp3Response = unknown;
-
-export type V1TranscriptGetAudioWaveformData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetAudioWaveformResponse = AudioWaveform;
-
-export type V1TranscriptGetParticipantsData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetParticipantsResponse = Array;
-
-export type V1TranscriptAddParticipantData = {
- requestBody: CreateParticipant;
- transcriptId: string;
-};
-
-export type V1TranscriptAddParticipantResponse = Participant;
-
-export type V1TranscriptGetParticipantData = {
- participantId: string;
- transcriptId: string;
-};
-
-export type V1TranscriptGetParticipantResponse = Participant;
-
-export type V1TranscriptUpdateParticipantData = {
- participantId: string;
- requestBody: UpdateParticipant;
- transcriptId: string;
-};
-
-export type V1TranscriptUpdateParticipantResponse = Participant;
-
-export type V1TranscriptDeleteParticipantData = {
- participantId: string;
- transcriptId: string;
-};
-
-export type V1TranscriptDeleteParticipantResponse = DeletionStatus;
-
-export type V1TranscriptAssignSpeakerData = {
- requestBody: SpeakerAssignment;
- transcriptId: string;
-};
-
-export type V1TranscriptAssignSpeakerResponse = SpeakerAssignmentStatus;
-
-export type V1TranscriptMergeSpeakerData = {
- requestBody: SpeakerMerge;
- transcriptId: string;
-};
-
-export type V1TranscriptMergeSpeakerResponse = SpeakerAssignmentStatus;
-
-export type V1TranscriptRecordUploadData = {
- chunkNumber: number;
- formData: Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post;
- totalChunks: number;
- transcriptId: string;
-};
-
-export type V1TranscriptRecordUploadResponse = unknown;
-
-export type V1TranscriptGetWebsocketEventsData = {
- transcriptId: string;
-};
-
-export type V1TranscriptGetWebsocketEventsResponse = unknown;
-
-export type V1TranscriptRecordWebrtcData = {
- requestBody: RtcOffer;
- transcriptId: string;
-};
-
-export type V1TranscriptRecordWebrtcResponse = unknown;
-
-export type V1TranscriptProcessData = {
- transcriptId: string;
-};
-
-export type V1TranscriptProcessResponse = unknown;
-
-export type V1UserMeResponse = UserInfo | null;
-
-export type V1ZulipGetStreamsResponse = Array;
-
-export type V1ZulipGetTopicsData = {
- streamId: number;
-};
-
-export type V1ZulipGetTopicsResponse = Array;
-
-export type V1WherebyWebhookData = {
- requestBody: WherebyWebhookEvent;
-};
-
-export type V1WherebyWebhookResponse = unknown;
-
-export type $OpenApiTs = {
- "/metrics": {
- get: {
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- };
- };
- };
- "/v1/meetings/{meeting_id}/consent": {
- post: {
- req: V1MeetingAudioConsentData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/rooms": {
- get: {
- req: V1RoomsListData;
- res: {
- /**
- * Successful Response
- */
- 200: Page_RoomDetails_;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- post: {
- req: V1RoomsCreateData;
- res: {
- /**
- * Successful Response
- */
- 200: Room;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/rooms/{room_id}": {
- get: {
- req: V1RoomsGetData;
- res: {
- /**
- * Successful Response
- */
- 200: RoomDetails;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- patch: {
- req: V1RoomsUpdateData;
- res: {
- /**
- * Successful Response
- */
- 200: RoomDetails;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- delete: {
- req: V1RoomsDeleteData;
- res: {
- /**
- * Successful Response
- */
- 200: DeletionStatus;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/rooms/{room_name}/meeting": {
- post: {
- req: V1RoomsCreateMeetingData;
- res: {
- /**
- * Successful Response
- */
- 200: Meeting;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/rooms/{room_id}/webhook/test": {
- post: {
- req: V1RoomsTestWebhookData;
- res: {
- /**
- * Successful Response
- */
- 200: WebhookTestResult;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts": {
- get: {
- req: V1TranscriptsListData;
- res: {
- /**
- * Successful Response
- */
- 200: Page_GetTranscriptMinimal_;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- post: {
- req: V1TranscriptsCreateData;
- res: {
- /**
- * Successful Response
- */
- 200: GetTranscript;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/search": {
- get: {
- req: V1TranscriptsSearchData;
- res: {
- /**
- * Successful Response
- */
- 200: SearchResponse;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}": {
- get: {
- req: V1TranscriptGetData;
- res: {
- /**
- * Successful Response
- */
- 200: GetTranscript;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- patch: {
- req: V1TranscriptUpdateData;
- res: {
- /**
- * Successful Response
- */
- 200: GetTranscript;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- delete: {
- req: V1TranscriptDeleteData;
- res: {
- /**
- * Successful Response
- */
- 200: DeletionStatus;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/topics": {
- get: {
- req: V1TranscriptGetTopicsData;
- res: {
- /**
- * Successful Response
- */
- 200: Array;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/topics/with-words": {
- get: {
- req: V1TranscriptGetTopicsWithWordsData;
- res: {
- /**
- * Successful Response
- */
- 200: Array;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker": {
- get: {
- req: V1TranscriptGetTopicsWithWordsPerSpeakerData;
- res: {
- /**
- * Successful Response
- */
- 200: GetTranscriptTopicWithWordsPerSpeaker;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/zulip": {
- post: {
- req: V1TranscriptPostToZulipData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/audio/mp3": {
- head: {
- req: V1TranscriptHeadAudioMp3Data;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- get: {
- req: V1TranscriptGetAudioMp3Data;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/audio/waveform": {
- get: {
- req: V1TranscriptGetAudioWaveformData;
- res: {
- /**
- * Successful Response
- */
- 200: AudioWaveform;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/participants": {
- get: {
- req: V1TranscriptGetParticipantsData;
- res: {
- /**
- * Successful Response
- */
- 200: Array;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- post: {
- req: V1TranscriptAddParticipantData;
- res: {
- /**
- * Successful Response
- */
- 200: Participant;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/participants/{participant_id}": {
- get: {
- req: V1TranscriptGetParticipantData;
- res: {
- /**
- * Successful Response
- */
- 200: Participant;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- patch: {
- req: V1TranscriptUpdateParticipantData;
- res: {
- /**
- * Successful Response
- */
- 200: Participant;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- delete: {
- req: V1TranscriptDeleteParticipantData;
- res: {
- /**
- * Successful Response
- */
- 200: DeletionStatus;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/speaker/assign": {
- patch: {
- req: V1TranscriptAssignSpeakerData;
- res: {
- /**
- * Successful Response
- */
- 200: SpeakerAssignmentStatus;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/speaker/merge": {
- patch: {
- req: V1TranscriptMergeSpeakerData;
- res: {
- /**
- * Successful Response
- */
- 200: SpeakerAssignmentStatus;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/record/upload": {
- post: {
- req: V1TranscriptRecordUploadData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/events": {
- get: {
- req: V1TranscriptGetWebsocketEventsData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/record/webrtc": {
- post: {
- req: V1TranscriptRecordWebrtcData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/transcripts/{transcript_id}/process": {
- post: {
- req: V1TranscriptProcessData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/me": {
- get: {
- res: {
- /**
- * Successful Response
- */
- 200: UserInfo | null;
- };
- };
- };
- "/v1/zulip/streams": {
- get: {
- res: {
- /**
- * Successful Response
- */
- 200: Array;
- };
- };
- };
- "/v1/zulip/streams/{stream_id}/topics": {
- get: {
- req: V1ZulipGetTopicsData;
- res: {
- /**
- * Successful Response
- */
- 200: Array;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
- "/v1/whereby": {
- post: {
- req: V1WherebyWebhookData;
- res: {
- /**
- * Successful Response
- */
- 200: unknown;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
-};
diff --git a/www/app/api/urls.ts b/www/app/api/urls.ts
index bd0a910c..89ce5af8 100644
--- a/www/app/api/urls.ts
+++ b/www/app/api/urls.ts
@@ -1,2 +1 @@
-// TODO better connection with generated schema; it's duplication
export const RECORD_A_MEETING_URL = "/transcripts/new" as const;
diff --git a/www/app/layout.tsx b/www/app/layout.tsx
index f73b8813..62175be9 100644
--- a/www/app/layout.tsx
+++ b/www/app/layout.tsx
@@ -1,7 +1,6 @@
import "./styles/globals.scss";
import { Metadata, Viewport } from "next";
import { Poppins } from "next/font/google";
-import SessionProvider from "./lib/SessionProvider";
import { ErrorProvider } from "./(errors)/errorContext";
import ErrorMessage from "./(errors)/errorMessage";
import { DomainContextProvider } from "./domainContext";
@@ -74,18 +73,16 @@ export default async function RootLayout({
return (
-
-
-
- "something went really wrong"
}>
-
-
- {children}
-
-
-
-
-
+
+
+ "something went really wrong"}>
+
+
+ {children}
+
+
+
+
);
diff --git a/www/app/lib/AuthProvider.tsx b/www/app/lib/AuthProvider.tsx
new file mode 100644
index 00000000..96f49f87
--- /dev/null
+++ b/www/app/lib/AuthProvider.tsx
@@ -0,0 +1,104 @@
+"use client";
+
+import { createContext, useContext } from "react";
+import { useSession as useNextAuthSession } from "next-auth/react";
+import { signOut, signIn } from "next-auth/react";
+import { configureApiAuth } from "./apiClient";
+import { assertCustomSession, CustomSession } from "./types";
+import { Session } from "next-auth";
+import { SessionAutoRefresh } from "./SessionAutoRefresh";
+import { REFRESH_ACCESS_TOKEN_ERROR } from "./auth";
+
+type AuthContextType = (
+ | { status: "loading" }
+ | { status: "refreshing" }
+ | { status: "unauthenticated"; error?: string }
+ | {
+ status: "authenticated";
+ accessToken: string;
+ accessTokenExpires: number;
+ user: CustomSession["user"];
+ }
+) & {
+ update: () => Promise;
+ signIn: typeof signIn;
+ signOut: typeof signOut;
+};
+
+const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: React.ReactNode }) {
+ const { data: session, status, update } = useNextAuthSession();
+ const customSession = session ? assertCustomSession(session) : null;
+
+ const contextValue: AuthContextType = {
+ ...(() => {
+ switch (status) {
+ case "loading": {
+ const sessionIsHere = !!customSession;
+ switch (sessionIsHere) {
+ case false: {
+ return { status };
+ }
+ case true: {
+ return { status: "refreshing" as const };
+ }
+ default: {
+ const _: never = sessionIsHere;
+ throw new Error("unreachable");
+ }
+ }
+ }
+ case "authenticated": {
+ if (customSession?.error === REFRESH_ACCESS_TOKEN_ERROR) {
+ // token had expired but next auth still returns "authenticated" so show user unauthenticated state
+ return {
+ status: "unauthenticated" as const,
+ };
+ } else if (customSession?.accessToken) {
+ return {
+ status,
+ accessToken: customSession.accessToken,
+ accessTokenExpires: customSession.accessTokenExpires,
+ user: customSession.user,
+ };
+ } else {
+ console.warn(
+ "illegal state: authenticated but have no session/or access token. ignoring",
+ );
+ return { status: "unauthenticated" as const };
+ }
+ }
+ case "unauthenticated": {
+ return { status: "unauthenticated" as const };
+ }
+ default: {
+ const _: never = status;
+ throw new Error("unreachable");
+ }
+ }
+ })(),
+ update,
+ signIn,
+ signOut,
+ };
+
+ // not useEffect, we need it ASAP
+ configureApiAuth(
+ contextValue.status === "authenticated" ? contextValue.accessToken : null,
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error("useAuth must be used within an AuthProvider");
+ }
+ return context;
+}
diff --git a/www/app/lib/SessionAutoRefresh.tsx b/www/app/lib/SessionAutoRefresh.tsx
index 1e230d6c..fd29367f 100644
--- a/www/app/lib/SessionAutoRefresh.tsx
+++ b/www/app/lib/SessionAutoRefresh.tsx
@@ -1,5 +1,5 @@
/**
- * This is a custom hook that automatically refreshes the session when the access token is about to expire.
+ * This is a custom provider that automatically refreshes the session when the access token is about to expire.
* When communicating with the reflector API, we need to ensure that the access token is always valid.
*
* We could have implemented that as an interceptor on the API client, but not everything is using the
@@ -7,30 +7,38 @@
*/
"use client";
-import { useSession } from "next-auth/react";
import { useEffect } from "react";
-import { CustomSession } from "./types";
+import { useAuth } from "./AuthProvider";
+import { REFRESH_ACCESS_TOKEN_BEFORE } from "./auth";
-export function SessionAutoRefresh({
- children,
- refreshInterval = 20 /* seconds */,
-}) {
- const { data: session, update } = useSession();
- const customSession = session as CustomSession;
- const accessTokenExpires = customSession?.accessTokenExpires;
+const REFRESH_BEFORE = REFRESH_ACCESS_TOKEN_BEFORE;
+
+export function SessionAutoRefresh({ children }) {
+ const auth = useAuth();
+ const accessTokenExpires =
+ auth.status === "authenticated" ? auth.accessTokenExpires : null;
useEffect(() => {
+ // technical value for how often the setInterval will be polling news - not too fast (no spam in case of errors)
+ // and not too slow (debuggable)
+ const INTERVAL_REFRESH_MS = 5000;
const interval = setInterval(() => {
- if (accessTokenExpires) {
+ if (accessTokenExpires !== null) {
const timeLeft = accessTokenExpires - Date.now();
- if (timeLeft < refreshInterval * 1000) {
- update();
+ if (timeLeft < REFRESH_BEFORE) {
+ auth
+ .update()
+ .then(() => {})
+ .catch((e) => {
+ // note: 401 won't be considered error here
+ console.error("error refreshing auth token", e);
+ });
}
}
- }, refreshInterval * 1000);
+ }, INTERVAL_REFRESH_MS);
return () => clearInterval(interval);
- }, [accessTokenExpires, refreshInterval, update]);
+ }, [accessTokenExpires, auth.update]);
return children;
}
diff --git a/www/app/lib/SessionProvider.tsx b/www/app/lib/SessionProvider.tsx
deleted file mode 100644
index 9c95fbc8..00000000
--- a/www/app/lib/SessionProvider.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-"use client";
-import { SessionProvider as SessionProviderNextAuth } from "next-auth/react";
-import { SessionAutoRefresh } from "./SessionAutoRefresh";
-
-export default function SessionProvider({ children }) {
- return (
-
- {children}
-
- );
-}
diff --git a/www/app/lib/__tests__/redisTokenCache.test.ts b/www/app/lib/__tests__/redisTokenCache.test.ts
new file mode 100644
index 00000000..8ca8e8a1
--- /dev/null
+++ b/www/app/lib/__tests__/redisTokenCache.test.ts
@@ -0,0 +1,85 @@
+import {
+ getTokenCache,
+ setTokenCache,
+ deleteTokenCache,
+ TokenCacheEntry,
+ KV,
+} from "../redisTokenCache";
+
+const mockKV: KV & {
+ clear: () => void;
+} = (() => {
+ const data = new Map();
+ return {
+ async get(key: string): Promise {
+ return data.get(key) || null;
+ },
+
+ async setex(key: string, seconds_: number, value: string): Promise<"OK"> {
+ data.set(key, value);
+ return "OK";
+ },
+
+ async del(key: string): Promise {
+ const existed = data.has(key);
+ data.delete(key);
+ return existed ? 1 : 0;
+ },
+
+ clear() {
+ data.clear();
+ },
+ };
+})();
+
+describe("Redis Token Cache", () => {
+ beforeEach(() => {
+ mockKV.clear();
+ });
+
+ test("basic write/read - value written equals value read", async () => {
+ const testKey = "token:test-user-123";
+ const testValue: TokenCacheEntry = {
+ token: {
+ sub: "test-user-123",
+ name: "Test User",
+ email: "test@example.com",
+ accessToken: "access-token-123",
+ accessTokenExpires: Date.now() + 3600000, // 1 hour from now
+ refreshToken: "refresh-token-456",
+ },
+ timestamp: Date.now(),
+ };
+
+ await setTokenCache(mockKV, testKey, testValue);
+ const retrievedValue = await getTokenCache(mockKV, testKey);
+
+ expect(retrievedValue).not.toBeNull();
+ expect(retrievedValue).toEqual(testValue);
+ expect(retrievedValue?.token.accessToken).toBe(testValue.token.accessToken);
+ expect(retrievedValue?.token.sub).toBe(testValue.token.sub);
+ expect(retrievedValue?.timestamp).toBe(testValue.timestamp);
+ });
+
+ test("get returns null for non-existent key", async () => {
+ const result = await getTokenCache(mockKV, "non-existent-key");
+ expect(result).toBeNull();
+ });
+
+ test("delete removes token from cache", async () => {
+ const testKey = "token:delete-test";
+ const testValue: TokenCacheEntry = {
+ token: {
+ accessToken: "test-token",
+ accessTokenExpires: Date.now() + 3600000,
+ },
+ timestamp: Date.now(),
+ };
+
+ await setTokenCache(mockKV, testKey, testValue);
+ await deleteTokenCache(mockKV, testKey);
+
+ const result = await getTokenCache(mockKV, testKey);
+ expect(result).toBeNull();
+ });
+});
diff --git a/www/app/lib/apiClient.tsx b/www/app/lib/apiClient.tsx
new file mode 100644
index 00000000..cd97e151
--- /dev/null
+++ b/www/app/lib/apiClient.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import createClient from "openapi-fetch";
+import type { paths } from "../reflector-api";
+import {
+ queryOptions,
+ useMutation,
+ useQuery,
+ useSuspenseQuery,
+} from "@tanstack/react-query";
+import createFetchClient from "openapi-react-query";
+import { assertExistsAndNonEmptyString } from "./utils";
+import { isBuildPhase } from "./next";
+
+const API_URL = !isBuildPhase
+ ? assertExistsAndNonEmptyString(process.env.NEXT_PUBLIC_API_URL)
+ : "http://localhost";
+
+// Create the base openapi-fetch client with a default URL
+// The actual URL will be set via middleware in AuthProvider
+export const client = createClient({
+ baseUrl: API_URL,
+});
+
+export const $api = createFetchClient(client);
+
+let currentAuthToken: string | null | undefined = null;
+
+client.use({
+ onRequest({ request }) {
+ if (currentAuthToken) {
+ request.headers.set("Authorization", `Bearer ${currentAuthToken}`);
+ }
+ // XXX Only set Content-Type if not already set (FormData will set its own boundary)
+ // This is a work around for uploading file, we're passing a formdata
+ // but the content type was still application/json
+ if (
+ !request.headers.has("Content-Type") &&
+ !(request.body instanceof FormData)
+ ) {
+ request.headers.set("Content-Type", "application/json");
+ }
+ return request;
+ },
+});
+
+// the function contract: lightweight, idempotent
+export const configureApiAuth = (token: string | null | undefined) => {
+ currentAuthToken = token;
+};
diff --git a/www/app/lib/apiHooks.ts b/www/app/lib/apiHooks.ts
new file mode 100644
index 00000000..94d84c9b
--- /dev/null
+++ b/www/app/lib/apiHooks.ts
@@ -0,0 +1,618 @@
+"use client";
+
+import { $api } from "./apiClient";
+import { useError } from "../(errors)/errorContext";
+import { useQueryClient } from "@tanstack/react-query";
+import type { components } from "../reflector-api";
+import { useAuth } from "./AuthProvider";
+
+/*
+ * XXX error types returned from the hooks are not always correct; declared types are ValidationError but real type could be string or any other
+ * this is either a limitation or incorrect usage of Python json schema generator
+ * or, limitation or incorrect usage of .d type generator from json schema
+ * */
+
+const useAuthReady = () => {
+ const auth = useAuth();
+
+ return {
+ isAuthenticated: auth.status === "authenticated",
+ isLoading: auth.status === "loading",
+ };
+};
+
+export function useRoomsList(page: number = 1) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/rooms",
+ {
+ params: {
+ query: { page },
+ },
+ },
+ {
+ enabled: isAuthenticated,
+ },
+ );
+}
+
+type SourceKind = components["schemas"]["SourceKind"];
+
+export function useTranscriptsSearch(
+ q: string = "",
+ options: {
+ limit?: number;
+ offset?: number;
+ room_id?: string;
+ source_kind?: SourceKind;
+ } = {},
+) {
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/search",
+ {
+ params: {
+ query: {
+ q,
+ limit: options.limit,
+ offset: options.offset,
+ room_id: options.room_id,
+ source_kind: options.source_kind,
+ },
+ },
+ },
+ {
+ enabled: true,
+ },
+ );
+}
+
+export function useTranscriptDelete() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("delete", "/v1/transcripts/{transcript_id}", {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ["get", "/v1/transcripts/search"],
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error deleting the transcript");
+ },
+ });
+}
+
+export function useTranscriptProcess() {
+ const { setError } = useError();
+
+ return $api.useMutation("post", "/v1/transcripts/{transcript_id}/process", {
+ onError: (error) => {
+ setError(error as Error, "There was an error processing the transcript");
+ },
+ });
+}
+
+export function useTranscriptGet(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: {
+ path: {
+ transcript_id: transcriptId || "",
+ },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useRoomGet(roomId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/rooms/{room_id}",
+ {
+ params: {
+ path: { room_id: roomId || "" },
+ },
+ },
+ {
+ enabled: !!roomId && isAuthenticated,
+ },
+ );
+}
+
+export function useRoomTestWebhook() {
+ const { setError } = useError();
+
+ return $api.useMutation("post", "/v1/rooms/{room_id}/webhook/test", {
+ onError: (error) => {
+ setError(error as Error, "There was an error testing the webhook");
+ },
+ });
+}
+
+export function useRoomCreate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("post", "/v1/rooms", {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error creating the room");
+ },
+ });
+}
+
+export function useRoomUpdate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("patch", "/v1/rooms/{room_id}", {
+ onSuccess: async (room) => {
+ await Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
+ }),
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/rooms/{room_id}", {
+ params: {
+ path: {
+ room_id: room.id,
+ },
+ },
+ }).queryKey,
+ }),
+ ]);
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error updating the room");
+ },
+ });
+}
+
+export function useRoomDelete() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("delete", "/v1/rooms/{room_id}", {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error deleting the room");
+ },
+ });
+}
+
+export function useZulipStreams() {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/zulip/streams",
+ {},
+ {
+ enabled: isAuthenticated,
+ },
+ );
+}
+
+export function useZulipTopics(streamId: number | null) {
+ const { isAuthenticated } = useAuthReady();
+ const enabled = !!streamId && isAuthenticated;
+ return $api.useQuery(
+ "get",
+ "/v1/zulip/streams/{stream_id}/topics",
+ {
+ params: {
+ path: {
+ stream_id: enabled ? streamId : 0,
+ },
+ },
+ },
+ {
+ enabled,
+ },
+ );
+}
+
+export function useTranscriptUpdate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("patch", "/v1/transcripts/{transcript_id}", {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/transcripts/{transcript_id}", {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ }).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error updating the transcript");
+ },
+ });
+}
+
+export function useTranscriptPostToZulip() {
+ const { setError } = useError();
+
+ // @ts-ignore - Zulip endpoint not in OpenAPI spec
+ return $api.useMutation("post", "/v1/transcripts/{transcript_id}/zulip", {
+ onError: (error) => {
+ setError(error as Error, "There was an error posting to Zulip");
+ },
+ });
+}
+
+export function useTranscriptUploadAudio() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "post",
+ "/v1/transcripts/{transcript_id}/record/upload",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error uploading the audio file");
+ },
+ },
+ );
+}
+
+export function useTranscriptWaveform(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/audio/waveform",
+ {
+ params: {
+ path: { transcript_id: transcriptId || "" },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptMP3(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/audio/mp3",
+ {
+ params: {
+ path: { transcript_id: transcriptId || "" },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptTopics(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/topics",
+ {
+ params: {
+ path: { transcript_id: transcriptId || "" },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptTopicsWithWords(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/topics/with-words",
+ {
+ params: {
+ path: { transcript_id: transcriptId || "" },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptTopicsWithWordsPerSpeaker(
+ transcriptId: string | null,
+ topicId: string | null,
+) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker",
+ {
+ params: {
+ path: {
+ transcript_id: transcriptId || "",
+ topic_id: topicId || "",
+ },
+ },
+ },
+ {
+ enabled: !!transcriptId && !!topicId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptParticipants(transcriptId: string | null) {
+ const { isAuthenticated } = useAuthReady();
+
+ return $api.useQuery(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: transcriptId || "" },
+ },
+ },
+ {
+ enabled: !!transcriptId && isAuthenticated,
+ },
+ );
+}
+
+export function useTranscriptParticipantUpdate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "patch",
+ "/v1/transcripts/{transcript_id}/participants/{participant_id}",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error updating the participant");
+ },
+ },
+ );
+}
+
+export function useTranscriptParticipantCreate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "post",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error creating the participant");
+ },
+ },
+ );
+}
+
+export function useTranscriptParticipantDelete() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "delete",
+ "/v1/transcripts/{transcript_id}/participants/{participant_id}",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error deleting the participant");
+ },
+ },
+ );
+}
+
+export function useTranscriptSpeakerAssign() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "patch",
+ "/v1/transcripts/{transcript_id}/speaker/assign",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error assigning the speaker");
+ },
+ },
+ );
+}
+
+export function useTranscriptSpeakerMerge() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation(
+ "patch",
+ "/v1/transcripts/{transcript_id}/speaker/merge",
+ {
+ onSuccess: (data, variables) => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions(
+ "get",
+ "/v1/transcripts/{transcript_id}/participants",
+ {
+ params: {
+ path: { transcript_id: variables.params.path.transcript_id },
+ },
+ },
+ ).queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error merging speakers");
+ },
+ },
+ );
+}
+
+export function useMeetingAudioConsent() {
+ const { setError } = useError();
+
+ return $api.useMutation("post", "/v1/meetings/{meeting_id}/consent", {
+ onError: (error) => {
+ setError(error as Error, "There was an error recording consent");
+ },
+ });
+}
+
+export function useTranscriptWebRTC() {
+ const { setError } = useError();
+
+ return $api.useMutation(
+ "post",
+ "/v1/transcripts/{transcript_id}/record/webrtc",
+ {
+ onError: (error) => {
+ setError(error as Error, "There was an error with WebRTC connection");
+ },
+ },
+ );
+}
+
+export function useTranscriptCreate() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("post", "/v1/transcripts", {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ["get", "/v1/transcripts/search"],
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error creating the transcript");
+ },
+ });
+}
+
+export function useRoomsCreateMeeting() {
+ const { setError } = useError();
+ const queryClient = useQueryClient();
+
+ return $api.useMutation("post", "/v1/rooms/{room_name}/meeting", {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
+ });
+ },
+ onError: (error) => {
+ setError(error as Error, "There was an error creating the meeting");
+ },
+ });
+}
diff --git a/www/app/lib/auth.ts b/www/app/lib/auth.ts
index 9169c694..f6e60513 100644
--- a/www/app/lib/auth.ts
+++ b/www/app/lib/auth.ts
@@ -1,157 +1,13 @@
-// import { kv } from "@vercel/kv";
-import Redlock, { ResourceLockedError } from "redlock";
-import { AuthOptions } from "next-auth";
-import AuthentikProvider from "next-auth/providers/authentik";
-import { JWT } from "next-auth/jwt";
-import { JWTWithAccessToken, CustomSession } from "./types";
-import Redis from "ioredis";
+export const REFRESH_ACCESS_TOKEN_ERROR = "RefreshAccessTokenError" as const;
+// 4 min is 1 min less than default authentic value. here we assume that authentic won't be set to access tokens < 4 min
+export const REFRESH_ACCESS_TOKEN_BEFORE = 4 * 60 * 1000;
-const PRETIMEOUT = 60; // seconds before token expires to refresh it
-const DEFAULT_REDIS_KEY_TIMEOUT = 60 * 60 * 24 * 30; // 30 days (refresh token expires in 30 days)
-const kv = new Redis(process.env.KV_URL || "", {
- tls: {},
-});
-const redlock = new Redlock([kv], {});
+export const LOGIN_REQUIRED_PAGES = [
+ "/transcripts/[!new]",
+ "/browse(.*)",
+ "/rooms(.*)",
+];
-redlock.on("error", (error) => {
- if (error instanceof ResourceLockedError) {
- return;
- }
-
- // Log all other errors.
- console.error(error);
-});
-
-export const authOptions: AuthOptions = {
- providers: [
- AuthentikProvider({
- clientId: process.env.AUTHENTIK_CLIENT_ID as string,
- clientSecret: process.env.AUTHENTIK_CLIENT_SECRET as string,
- issuer: process.env.AUTHENTIK_ISSUER,
- authorization: {
- params: {
- scope: "openid email profile offline_access",
- },
- },
- }),
- ],
- session: {
- strategy: "jwt",
- },
- callbacks: {
- async jwt({ token, account, user }) {
- const extendedToken = token as JWTWithAccessToken;
- if (account && user) {
- // called only on first login
- // XXX account.expires_in used in example is not defined for authentik backend, but expires_at is
- const expiresAt = (account.expires_at as number) - PRETIMEOUT;
- const jwtToken = {
- ...extendedToken,
- accessToken: account.access_token,
- accessTokenExpires: expiresAt * 1000,
- refreshToken: account.refresh_token,
- };
- kv.set(
- `token:${jwtToken.sub}`,
- JSON.stringify(jwtToken),
- "EX",
- DEFAULT_REDIS_KEY_TIMEOUT,
- );
- return jwtToken;
- }
-
- if (Date.now() < extendedToken.accessTokenExpires) {
- return token;
- }
-
- // access token has expired, try to update it
- return await redisLockedrefreshAccessToken(token);
- },
- async session({ session, token }) {
- const extendedToken = token as JWTWithAccessToken;
- const customSession = session as CustomSession;
- customSession.accessToken = extendedToken.accessToken;
- customSession.accessTokenExpires = extendedToken.accessTokenExpires;
- customSession.error = extendedToken.error;
- customSession.user = {
- id: extendedToken.sub,
- name: extendedToken.name,
- email: extendedToken.email,
- };
- return customSession;
- },
- },
-};
-
-async function redisLockedrefreshAccessToken(token: JWT) {
- return await redlock.using(
- [token.sub as string, "jwt-refresh"],
- 5000,
- async () => {
- const redisToken = await kv.get(`token:${token.sub}`);
- const currentToken = JSON.parse(
- redisToken as string,
- ) as JWTWithAccessToken;
-
- // if there is multiple requests for the same token, it may already have been refreshed
- if (Date.now() < currentToken.accessTokenExpires) {
- return currentToken;
- }
-
- // now really do the request
- const newToken = await refreshAccessToken(currentToken);
- await kv.set(
- `token:${currentToken.sub}`,
- JSON.stringify(newToken),
- "EX",
- DEFAULT_REDIS_KEY_TIMEOUT,
- );
- return newToken;
- },
- );
-}
-
-async function refreshAccessToken(token: JWT): Promise {
- try {
- const url = `${process.env.AUTHENTIK_REFRESH_TOKEN_URL}`;
-
- const options = {
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- body: new URLSearchParams({
- client_id: process.env.AUTHENTIK_CLIENT_ID as string,
- client_secret: process.env.AUTHENTIK_CLIENT_SECRET as string,
- grant_type: "refresh_token",
- refresh_token: token.refreshToken as string,
- }).toString(),
- method: "POST",
- };
-
- const response = await fetch(url, options);
- if (!response.ok) {
- console.error(
- new Date().toISOString(),
- "Failed to refresh access token. Response status:",
- response.status,
- );
- const responseBody = await response.text();
- console.error(new Date().toISOString(), "Response body:", responseBody);
- throw new Error(`Failed to refresh access token: ${response.statusText}`);
- }
- const refreshedTokens = await response.json();
- return {
- ...token,
- accessToken: refreshedTokens.access_token,
- accessTokenExpires:
- Date.now() + (refreshedTokens.expires_in - PRETIMEOUT) * 1000,
- refreshToken: refreshedTokens.refresh_token,
- };
- } catch (error) {
- console.error("Error refreshing access token", error);
- return {
- ...token,
- error: "RefreshAccessTokenError",
- } as JWTWithAccessToken;
- }
-}
+export const PROTECTED_PAGES = new RegExp(
+ LOGIN_REQUIRED_PAGES.map((page) => `^${page}$`).join("|"),
+);
diff --git a/www/app/lib/authBackend.ts b/www/app/lib/authBackend.ts
new file mode 100644
index 00000000..af93b274
--- /dev/null
+++ b/www/app/lib/authBackend.ts
@@ -0,0 +1,178 @@
+import { AuthOptions } from "next-auth";
+import AuthentikProvider from "next-auth/providers/authentik";
+import type { JWT } from "next-auth/jwt";
+import { JWTWithAccessToken, CustomSession } from "./types";
+import { assertExists, assertExistsAndNonEmptyString } from "./utils";
+import {
+ REFRESH_ACCESS_TOKEN_BEFORE,
+ REFRESH_ACCESS_TOKEN_ERROR,
+} from "./auth";
+import {
+ getTokenCache,
+ setTokenCache,
+ deleteTokenCache,
+} from "./redisTokenCache";
+import { tokenCacheRedis } from "./redisClient";
+import { isBuildPhase } from "./next";
+
+// REFRESH_ACCESS_TOKEN_BEFORE because refresh is based on access token expiration (imagine we cache it 30 days)
+const TOKEN_CACHE_TTL = REFRESH_ACCESS_TOKEN_BEFORE;
+
+const refreshLocks = new Map>();
+
+const CLIENT_ID = !isBuildPhase
+ ? assertExistsAndNonEmptyString(process.env.AUTHENTIK_CLIENT_ID)
+ : "noop";
+const CLIENT_SECRET = !isBuildPhase
+ ? assertExistsAndNonEmptyString(process.env.AUTHENTIK_CLIENT_SECRET)
+ : "noop";
+
+export const authOptions: AuthOptions = {
+ providers: [
+ AuthentikProvider({
+ clientId: CLIENT_ID,
+ clientSecret: CLIENT_SECRET,
+ issuer: process.env.AUTHENTIK_ISSUER,
+ authorization: {
+ params: {
+ scope: "openid email profile offline_access",
+ },
+ },
+ }),
+ ],
+ session: {
+ strategy: "jwt",
+ },
+ callbacks: {
+ async jwt({ token, account, user }) {
+ const KEY = `token:${token.sub}`;
+
+ if (account && user) {
+ // called only on first login
+ // XXX account.expires_in used in example is not defined for authentik backend, but expires_at is
+ const expiresAtS = assertExists(account.expires_at);
+ const expiresAtMs = expiresAtS * 1000;
+ if (!account.access_token) {
+ await deleteTokenCache(tokenCacheRedis, KEY);
+ } else {
+ const jwtToken: JWTWithAccessToken = {
+ ...token,
+ accessToken: account.access_token,
+ accessTokenExpires: expiresAtMs,
+ refreshToken: account.refresh_token,
+ };
+ await setTokenCache(tokenCacheRedis, KEY, {
+ token: jwtToken,
+ timestamp: Date.now(),
+ });
+ return jwtToken;
+ }
+ }
+
+ const currentToken = await getTokenCache(tokenCacheRedis, KEY);
+ if (currentToken && Date.now() < currentToken.token.accessTokenExpires) {
+ return currentToken.token;
+ }
+
+ // access token has expired, try to update it
+ return await lockedRefreshAccessToken(token);
+ },
+ async session({ session, token }) {
+ const extendedToken = token as JWTWithAccessToken;
+ return {
+ ...session,
+ accessToken: extendedToken.accessToken,
+ accessTokenExpires: extendedToken.accessTokenExpires,
+ error: extendedToken.error,
+ user: {
+ id: assertExists(extendedToken.sub),
+ name: extendedToken.name,
+ email: extendedToken.email,
+ },
+ } satisfies CustomSession;
+ },
+ },
+};
+
+async function lockedRefreshAccessToken(
+ token: JWT,
+): Promise {
+ const lockKey = `${token.sub}-refresh`;
+
+ const existingRefresh = refreshLocks.get(lockKey);
+ if (existingRefresh) {
+ return await existingRefresh;
+ }
+
+ const refreshPromise = (async () => {
+ try {
+ const cached = await getTokenCache(tokenCacheRedis, `token:${token.sub}`);
+ if (cached) {
+ if (Date.now() - cached.timestamp > TOKEN_CACHE_TTL) {
+ await deleteTokenCache(tokenCacheRedis, `token:${token.sub}`);
+ } else if (Date.now() < cached.token.accessTokenExpires) {
+ return cached.token;
+ }
+ }
+
+ const currentToken = cached?.token || (token as JWTWithAccessToken);
+ const newToken = await refreshAccessToken(currentToken);
+
+ await setTokenCache(tokenCacheRedis, `token:${token.sub}`, {
+ token: newToken,
+ timestamp: Date.now(),
+ });
+
+ return newToken;
+ } finally {
+ setTimeout(() => refreshLocks.delete(lockKey), 100);
+ }
+ })();
+
+ refreshLocks.set(lockKey, refreshPromise);
+ return refreshPromise;
+}
+
+async function refreshAccessToken(token: JWT): Promise {
+ try {
+ const url = `${process.env.AUTHENTIK_REFRESH_TOKEN_URL}`;
+
+ const options = {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams({
+ client_id: process.env.AUTHENTIK_CLIENT_ID as string,
+ client_secret: process.env.AUTHENTIK_CLIENT_SECRET as string,
+ grant_type: "refresh_token",
+ refresh_token: token.refreshToken as string,
+ }).toString(),
+ method: "POST",
+ };
+
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ console.error(
+ new Date().toISOString(),
+ "Failed to refresh access token. Response status:",
+ response.status,
+ );
+ const responseBody = await response.text();
+ console.error(new Date().toISOString(), "Response body:", responseBody);
+ throw new Error(`Failed to refresh access token: ${response.statusText}`);
+ }
+ const refreshedTokens = await response.json();
+ return {
+ ...token,
+ accessToken: refreshedTokens.access_token,
+ accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
+ refreshToken: refreshedTokens.refresh_token,
+ };
+ } catch (error) {
+ console.error("Error refreshing access token", error);
+ return {
+ ...token,
+ error: REFRESH_ACCESS_TOKEN_ERROR,
+ } as JWTWithAccessToken;
+ }
+}
diff --git a/www/app/lib/edgeConfig.ts b/www/app/lib/edgeConfig.ts
index 2e31e146..f234a2cf 100644
--- a/www/app/lib/edgeConfig.ts
+++ b/www/app/lib/edgeConfig.ts
@@ -1,5 +1,5 @@
import { get } from "@vercel/edge-config";
-import { isDevelopment } from "./utils";
+import { isBuildPhase } from "./next";
type EdgeConfig = {
[domainWithDash: string]: {
@@ -29,12 +29,18 @@ export function edgeDomainToKey(domain: string) {
// get edge config server-side (prefer DomainContext when available), domain is the hostname
export async function getConfig() {
- const domain = new URL(process.env.NEXT_PUBLIC_SITE_URL!).hostname;
-
if (process.env.NEXT_PUBLIC_ENV === "development") {
- return require("../../config").localConfig;
+ try {
+ return require("../../config").localConfig;
+ } catch (e) {
+ // next build() WILL try to execute the require above even if conditionally protected
+ // but thank god it at least runs catch{} block properly
+ if (!isBuildPhase) throw new Error(e);
+ return require("../../config-template").localConfig;
+ }
}
+ const domain = new URL(process.env.NEXT_PUBLIC_SITE_URL!).hostname;
let config = await get(edgeDomainToKey(domain));
if (typeof config !== "object") {
diff --git a/www/app/lib/next.ts b/www/app/lib/next.ts
new file mode 100644
index 00000000..91d88bd2
--- /dev/null
+++ b/www/app/lib/next.ts
@@ -0,0 +1,2 @@
+// next.js tries to run all the lib code during build phase; we don't always want it when e.g. we have connections initialized we don't want to have
+export const isBuildPhase = process.env.NEXT_PHASE?.includes("build");
diff --git a/www/app/lib/queryClient.tsx b/www/app/lib/queryClient.tsx
new file mode 100644
index 00000000..bd5946e0
--- /dev/null
+++ b/www/app/lib/queryClient.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { QueryClient } from "@tanstack/react-query";
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000, // 1 minute
+ gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
+ retry: 1,
+ refetchOnWindowFocus: false,
+ },
+ mutations: {
+ retry: 0,
+ },
+ },
+});
diff --git a/www/app/lib/redisClient.ts b/www/app/lib/redisClient.ts
new file mode 100644
index 00000000..1be36538
--- /dev/null
+++ b/www/app/lib/redisClient.ts
@@ -0,0 +1,46 @@
+import Redis from "ioredis";
+import { isBuildPhase } from "./next";
+
+export type RedisClient = Pick;
+
+const getRedisClient = (): RedisClient => {
+ const redisUrl = process.env.KV_URL;
+ if (!redisUrl) {
+ throw new Error("KV_URL environment variable is required");
+ }
+ const redis = new Redis(redisUrl, {
+ maxRetriesPerRequest: 3,
+ lazyConnect: true,
+ });
+
+ redis.on("error", (error) => {
+ console.error("Redis error:", error);
+ });
+
+ // not necessary but will indicate redis config errors by failfast at startup
+ // happens only once; after that connection is allowed to die and the lib is assumed to be able to restore it eventually
+ redis.connect().catch((e) => {
+ console.error("Failed to connect to Redis:", e);
+ process.exit(1);
+ });
+
+ return redis;
+};
+
+// next.js buildtime usage - we want to isolate next.js "build" time concepts here
+const noopClient: RedisClient = (() => {
+ const noopSetex: Redis["setex"] = async () => {
+ return "OK" as const;
+ };
+ const noopDel: Redis["del"] = async () => {
+ return 0;
+ };
+ return {
+ get: async () => {
+ return null;
+ },
+ setex: noopSetex,
+ del: noopDel,
+ };
+})();
+export const tokenCacheRedis = isBuildPhase ? noopClient : getRedisClient();
diff --git a/www/app/lib/redisTokenCache.ts b/www/app/lib/redisTokenCache.ts
new file mode 100644
index 00000000..4fa4e304
--- /dev/null
+++ b/www/app/lib/redisTokenCache.ts
@@ -0,0 +1,61 @@
+import { z } from "zod";
+import { REFRESH_ACCESS_TOKEN_BEFORE } from "./auth";
+
+const TokenCacheEntrySchema = z.object({
+ token: z.object({
+ sub: z.string().optional(),
+ name: z.string().nullish(),
+ email: z.string().nullish(),
+ accessToken: z.string(),
+ accessTokenExpires: z.number(),
+ refreshToken: z.string().optional(),
+ error: z.string().optional(),
+ }),
+ timestamp: z.number(),
+});
+
+const TokenCacheEntryCodec = z.codec(z.string(), TokenCacheEntrySchema, {
+ decode: (jsonString) => {
+ const parsed = JSON.parse(jsonString);
+ return TokenCacheEntrySchema.parse(parsed);
+ },
+ encode: (value) => JSON.stringify(value),
+});
+
+export type TokenCacheEntry = z.infer;
+
+export type KV = {
+ get(key: string): Promise;
+ setex(key: string, seconds: number, value: string): Promise<"OK">;
+ del(key: string): Promise;
+};
+
+export async function getTokenCache(
+ redis: KV,
+ key: string,
+): Promise {
+ const data = await redis.get(key);
+ if (!data) return null;
+
+ try {
+ return TokenCacheEntryCodec.decode(data);
+ } catch (error) {
+ console.error("Invalid token cache data:", error);
+ await redis.del(key);
+ return null;
+ }
+}
+
+export async function setTokenCache(
+ redis: KV,
+ key: string,
+ value: TokenCacheEntry,
+): Promise {
+ const encodedValue = TokenCacheEntryCodec.encode(value);
+ const ttlSeconds = Math.floor(REFRESH_ACCESS_TOKEN_BEFORE / 1000);
+ await redis.setex(key, ttlSeconds, encodedValue);
+}
+
+export async function deleteTokenCache(redis: KV, key: string): Promise {
+ await redis.del(key);
+}
diff --git a/www/app/lib/types.ts b/www/app/lib/types.ts
index 851ee5be..0576e186 100644
--- a/www/app/lib/types.ts
+++ b/www/app/lib/types.ts
@@ -1,10 +1,11 @@
-import { Session } from "next-auth";
-import { JWT } from "next-auth/jwt";
+import type { Session } from "next-auth";
+import type { JWT } from "next-auth/jwt";
+import { parseMaybeNonEmptyString } from "./utils";
export interface JWTWithAccessToken extends JWT {
accessToken: string;
accessTokenExpires: number;
- refreshToken: string;
+ refreshToken?: string;
error?: string;
}
@@ -12,9 +13,62 @@ export interface CustomSession extends Session {
accessToken: string;
accessTokenExpires: number;
error?: string;
- user: {
- id?: string;
- name?: string | null;
- email?: string | null;
+ user: Session["user"] & {
+ id: string;
};
}
+
+// assumption that JWT is JWTWithAccessToken - we set it in jwt callback of auth; typing isn't strong around there
+// but the assumption is crucial to auth working
+export const assertExtendedToken = (
+ t: T,
+): T & {
+ accessTokenExpires: number;
+ accessToken: string;
+} => {
+ if (
+ typeof (t as { accessTokenExpires: any }).accessTokenExpires === "number" &&
+ !isNaN((t as { accessTokenExpires: any }).accessTokenExpires) &&
+ typeof (
+ t as {
+ accessToken: any;
+ }
+ ).accessToken === "string" &&
+ parseMaybeNonEmptyString((t as { accessToken: any }).accessToken) !== null
+ ) {
+ return t as T & {
+ accessTokenExpires: number;
+ accessToken: string;
+ };
+ }
+ throw new Error("Token is not extended with access token");
+};
+
+export const assertExtendedTokenAndUserId = (
+ t: T,
+): T & {
+ accessTokenExpires: number;
+ accessToken: string;
+ user: U & {
+ id: string;
+ };
+} => {
+ const extendedToken = assertExtendedToken(t);
+ if (typeof (extendedToken.user as any)?.id === "string") {
+ return t as T & {
+ accessTokenExpires: number;
+ accessToken: string;
+ user: U & {
+ id: string;
+ };
+ };
+ }
+ throw new Error("Token is not extended with user id");
+};
+
+// best attempt to check the session is valid
+export const assertCustomSession = (s: S): CustomSession => {
+ const r = assertExtendedTokenAndUserId(s);
+ // no other checks for now
+ return r as CustomSession;
+};
diff --git a/www/app/lib/useApi.ts b/www/app/lib/useApi.ts
deleted file mode 100644
index 837ef84f..00000000
--- a/www/app/lib/useApi.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useSession, signOut } from "next-auth/react";
-import { useContext, useEffect, useState } from "react";
-import { DomainContext, featureEnabled } from "../domainContext";
-import { OpenApi, DefaultService } from "../api";
-import { CustomSession } from "./types";
-import useSessionStatus from "./useSessionStatus";
-import useSessionAccessToken from "./useSessionAccessToken";
-
-export default function useApi(): DefaultService | null {
- const api_url = useContext(DomainContext).api_url;
- const [api, setApi] = useState(null);
- const { isLoading, isAuthenticated } = useSessionStatus();
- const { accessToken, error } = useSessionAccessToken();
-
- if (!api_url) throw new Error("no API URL");
-
- useEffect(() => {
- if (error === "RefreshAccessTokenError") {
- signOut();
- }
- }, [error]);
-
- useEffect(() => {
- if (isLoading || (isAuthenticated && !accessToken)) {
- return;
- }
-
- const openApi = new OpenApi({
- BASE: api_url,
- TOKEN: accessToken || undefined,
- });
-
- setApi(openApi);
- }, [isLoading, isAuthenticated, accessToken]);
-
- return api?.default ?? null;
-}
diff --git a/www/app/lib/useLoginRequiredPages.ts b/www/app/lib/useLoginRequiredPages.ts
new file mode 100644
index 00000000..37ee96b1
--- /dev/null
+++ b/www/app/lib/useLoginRequiredPages.ts
@@ -0,0 +1,26 @@
+// for paths that are not supposed to be public
+import { PROTECTED_PAGES } from "./auth";
+import { usePathname } from "next/navigation";
+import { useAuth } from "./AuthProvider";
+import { useEffect } from "react";
+
+const HOME = "/" as const;
+
+export const useLoginRequiredPages = () => {
+ const pathname = usePathname();
+ const isProtected = PROTECTED_PAGES.test(pathname);
+ const auth = useAuth();
+ const isNotLoggedIn = auth.status === "unauthenticated";
+ // safety
+ const isLastDestination = pathname === HOME;
+ const shouldRedirect = isNotLoggedIn && isProtected && !isLastDestination;
+ useEffect(() => {
+ if (!shouldRedirect) return;
+ // on the backend, the redirect goes straight to the auth provider, but we don't have it because it's hidden inside next-auth middleware
+ // so we just "softly" lead the user to the main page
+ // warning: if HOME redirects somewhere else, we won't be protected by isLastDestination
+ window.location.href = HOME;
+ }, [shouldRedirect]);
+ // optionally save from blink, since window.location.href takes a bit of time
+ return shouldRedirect ? HOME : null;
+};
diff --git a/www/app/lib/useSessionAccessToken.ts b/www/app/lib/useSessionAccessToken.ts
deleted file mode 100644
index fc28c076..00000000
--- a/www/app/lib/useSessionAccessToken.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import { useSession as useNextAuthSession } from "next-auth/react";
-import { CustomSession } from "./types";
-
-export default function useSessionAccessToken() {
- const { data: session } = useNextAuthSession();
- const customSession = session as CustomSession;
- const naAccessToken = customSession?.accessToken;
- const naAccessTokenExpires = customSession?.accessTokenExpires;
- const naError = customSession?.error;
- const [accessToken, setAccessToken] = useState(null);
- const [accessTokenExpires, setAccessTokenExpires] = useState(
- null,
- );
- const [error, setError] = useState();
-
- useEffect(() => {
- if (naAccessToken !== accessToken) {
- setAccessToken(naAccessToken);
- }
- }, [naAccessToken]);
-
- useEffect(() => {
- if (naAccessTokenExpires !== accessTokenExpires) {
- setAccessTokenExpires(naAccessTokenExpires);
- }
- }, [naAccessTokenExpires]);
-
- useEffect(() => {
- if (naError !== error) {
- setError(naError);
- }
- }, [naError]);
-
- return {
- accessToken,
- accessTokenExpires,
- error,
- };
-}
diff --git a/www/app/lib/useSessionStatus.ts b/www/app/lib/useSessionStatus.ts
deleted file mode 100644
index 5629c025..00000000
--- a/www/app/lib/useSessionStatus.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import { useSession as useNextAuthSession } from "next-auth/react";
-import { Session } from "next-auth";
-
-export default function useSessionStatus() {
- const { status: naStatus } = useNextAuthSession();
- const [status, setStatus] = useState("loading");
-
- useEffect(() => {
- if (naStatus !== "loading" && naStatus !== status) {
- setStatus(naStatus);
- }
- }, [naStatus]);
-
- return {
- status,
- isLoading: status === "loading",
- isAuthenticated: status === "authenticated",
- };
-}
diff --git a/www/app/lib/useSessionUser.ts b/www/app/lib/useSessionUser.ts
deleted file mode 100644
index 2da299f5..00000000
--- a/www/app/lib/useSessionUser.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import { useSession as useNextAuthSession } from "next-auth/react";
-import { Session } from "next-auth";
-
-// user type with id, name, email
-export interface User {
- id?: string | null;
- name?: string | null;
- email?: string | null;
-}
-
-export default function useSessionUser() {
- const { data: session } = useNextAuthSession();
- const [user, setUser] = useState(null);
-
- useEffect(() => {
- if (!session?.user) {
- setUser(null);
- return;
- }
- if (JSON.stringify(session.user) !== JSON.stringify(user)) {
- setUser(session.user);
- }
- }, [session]);
-
- return {
- id: user?.id,
- name: user?.name,
- email: user?.email,
- };
-}
diff --git a/www/app/lib/useUserName.ts b/www/app/lib/useUserName.ts
new file mode 100644
index 00000000..80814281
--- /dev/null
+++ b/www/app/lib/useUserName.ts
@@ -0,0 +1,7 @@
+import { useAuth } from "./AuthProvider";
+
+export const useUserName = (): string | null | undefined => {
+ const auth = useAuth();
+ if (auth.status !== "authenticated") return undefined;
+ return auth.user?.name || null;
+};
diff --git a/www/app/lib/utils.ts b/www/app/lib/utils.ts
index 80d0d91b..122ab234 100644
--- a/www/app/lib/utils.ts
+++ b/www/app/lib/utils.ts
@@ -137,9 +137,28 @@ export function extractDomain(url) {
}
}
-export function assertExists(value: T | null | undefined, err?: string): T {
+export type NonEmptyString = string & { __brand: "NonEmptyString" };
+export const parseMaybeNonEmptyString = (
+ s: string,
+ trim = true,
+): NonEmptyString | null => {
+ s = trim ? s.trim() : s;
+ return s.length > 0 ? (s as NonEmptyString) : null;
+};
+export const parseNonEmptyString = (s: string, trim = true): NonEmptyString =>
+ assertExists(parseMaybeNonEmptyString(s, trim), "Expected non-empty string");
+
+export const assertExists = (
+ value: T | null | undefined,
+ err?: string,
+): T => {
if (value === null || value === undefined) {
throw new Error(`Assertion failed: ${err ?? "value is null or undefined"}`);
}
return value;
-}
+};
+
+export const assertExistsAndNonEmptyString = (
+ value: string | null | undefined,
+): NonEmptyString =>
+ parseNonEmptyString(assertExists(value, "Expected non-empty string"));
diff --git a/www/app/providers.tsx b/www/app/providers.tsx
index f0f1ea52..2e3b78eb 100644
--- a/www/app/providers.tsx
+++ b/www/app/providers.tsx
@@ -6,16 +6,26 @@ import system from "./styles/theme";
import { WherebyProvider } from "@whereby.com/browser-sdk/react";
import { Toaster } from "./components/ui/toaster";
import { NuqsAdapter } from "nuqs/adapters/next/app";
+import { QueryClientProvider } from "@tanstack/react-query";
+import { queryClient } from "./lib/queryClient";
+import { AuthProvider } from "./lib/AuthProvider";
+import { SessionProvider as SessionProviderNextAuth } from "next-auth/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
-
-
- {children}
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
+
);
}
diff --git a/www/app/reflector-api.d.ts b/www/app/reflector-api.d.ts
new file mode 100644
index 00000000..8a2cadb0
--- /dev/null
+++ b/www/app/reflector-api.d.ts
@@ -0,0 +1,2330 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/metrics": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Metrics
+ * @description Endpoint that serves Prometheus metrics.
+ */
+ get: operations["metrics"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/meetings/{meeting_id}/consent": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Meeting Audio Consent */
+ post: operations["v1_meeting_audio_consent"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/rooms": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Rooms List */
+ get: operations["v1_rooms_list"];
+ put?: never;
+ /** Rooms Create */
+ post: operations["v1_rooms_create"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/rooms/{room_id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Rooms Get */
+ get: operations["v1_rooms_get"];
+ put?: never;
+ post?: never;
+ /** Rooms Delete */
+ delete: operations["v1_rooms_delete"];
+ options?: never;
+ head?: never;
+ /** Rooms Update */
+ patch: operations["v1_rooms_update"];
+ trace?: never;
+ };
+ "/v1/rooms/{room_name}/meeting": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Rooms Create Meeting */
+ post: operations["v1_rooms_create_meeting"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/rooms/{room_id}/webhook/test": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Rooms Test Webhook
+ * @description Test webhook configuration by sending a sample payload.
+ */
+ post: operations["v1_rooms_test_webhook"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcripts List */
+ get: operations["v1_transcripts_list"];
+ put?: never;
+ /** Transcripts Create */
+ post: operations["v1_transcripts_create"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/search": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Transcripts Search
+ * @description Full-text search across transcript titles and content.
+ */
+ get: operations["v1_transcripts_search"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get */
+ get: operations["v1_transcript_get"];
+ put?: never;
+ post?: never;
+ /** Transcript Delete */
+ delete: operations["v1_transcript_delete"];
+ options?: never;
+ head?: never;
+ /** Transcript Update */
+ patch: operations["v1_transcript_update"];
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/topics": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Topics */
+ get: operations["v1_transcript_get_topics"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/topics/with-words": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Topics With Words */
+ get: operations["v1_transcript_get_topics_with_words"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Topics With Words Per Speaker */
+ get: operations["v1_transcript_get_topics_with_words_per_speaker"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/zulip": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Transcript Post To Zulip */
+ post: operations["v1_transcript_post_to_zulip"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/audio/mp3": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Audio Mp3 */
+ get: operations["v1_transcript_get_audio_mp3"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ /** Transcript Get Audio Mp3 */
+ head: operations["v1_transcript_head_audio_mp3"];
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/audio/waveform": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Audio Waveform */
+ get: operations["v1_transcript_get_audio_waveform"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/participants": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Participants */
+ get: operations["v1_transcript_get_participants"];
+ put?: never;
+ /** Transcript Add Participant */
+ post: operations["v1_transcript_add_participant"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/participants/{participant_id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Participant */
+ get: operations["v1_transcript_get_participant"];
+ put?: never;
+ post?: never;
+ /** Transcript Delete Participant */
+ delete: operations["v1_transcript_delete_participant"];
+ options?: never;
+ head?: never;
+ /** Transcript Update Participant */
+ patch: operations["v1_transcript_update_participant"];
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/speaker/assign": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ /** Transcript Assign Speaker */
+ patch: operations["v1_transcript_assign_speaker"];
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/speaker/merge": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ /** Transcript Merge Speaker */
+ patch: operations["v1_transcript_merge_speaker"];
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/record/upload": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Transcript Record Upload */
+ post: operations["v1_transcript_record_upload"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/events": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Transcript Get Websocket Events */
+ get: operations["v1_transcript_get_websocket_events"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/record/webrtc": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Transcript Record Webrtc */
+ post: operations["v1_transcript_record_webrtc"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/transcripts/{transcript_id}/process": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Transcript Process */
+ post: operations["v1_transcript_process"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/me": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** User Me */
+ get: operations["v1_user_me"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/zulip/streams": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Zulip Get Streams
+ * @description Get all Zulip streams.
+ */
+ get: operations["v1_zulip_get_streams"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/zulip/streams/{stream_id}/topics": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Zulip Get Topics
+ * @description Get all topics for a specific Zulip stream.
+ */
+ get: operations["v1_zulip_get_topics"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/whereby": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Whereby Webhook */
+ post: operations["v1_whereby_webhook"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record;
+export interface components {
+ schemas: {
+ /** AudioWaveform */
+ AudioWaveform: {
+ /** Data */
+ data: number[];
+ };
+ /** Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post */
+ Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post: {
+ /**
+ * Chunk
+ * Format: binary
+ */
+ chunk: string;
+ };
+ /** CreateParticipant */
+ CreateParticipant: {
+ /** Speaker */
+ speaker?: number | null;
+ /** Name */
+ name: string;
+ };
+ /** CreateRoom */
+ CreateRoom: {
+ /** Name */
+ name: string;
+ /** Zulip Auto Post */
+ zulip_auto_post: boolean;
+ /** Zulip Stream */
+ zulip_stream: string;
+ /** Zulip Topic */
+ zulip_topic: string;
+ /** Is Locked */
+ is_locked: boolean;
+ /** Room Mode */
+ room_mode: string;
+ /** Recording Type */
+ recording_type: string;
+ /** Recording Trigger */
+ recording_trigger: string;
+ /** Is Shared */
+ is_shared: boolean;
+ /** Webhook Url */
+ webhook_url: string;
+ /** Webhook Secret */
+ webhook_secret: string;
+ };
+ /** CreateTranscript */
+ CreateTranscript: {
+ /** Name */
+ name: string;
+ /**
+ * Source Language
+ * @default en
+ */
+ source_language: string;
+ /**
+ * Target Language
+ * @default en
+ */
+ target_language: string;
+ source_kind?: components["schemas"]["SourceKind"] | null;
+ };
+ /** DeletionStatus */
+ DeletionStatus: {
+ /** Status */
+ status: string;
+ };
+ /** GetTranscript */
+ GetTranscript: {
+ /** Id */
+ id: string;
+ /** User Id */
+ user_id: string | null;
+ /** Name */
+ name: string;
+ /**
+ * Status
+ * @enum {string}
+ */
+ status:
+ | "idle"
+ | "uploaded"
+ | "recording"
+ | "processing"
+ | "error"
+ | "ended";
+ /** Locked */
+ locked: boolean;
+ /** Duration */
+ duration: number;
+ /** Title */
+ title: string | null;
+ /** Short Summary */
+ short_summary: string | null;
+ /** Long Summary */
+ long_summary: string | null;
+ /** Created At */
+ created_at: string;
+ /**
+ * Share Mode
+ * @default private
+ */
+ share_mode: string;
+ /** Source Language */
+ source_language: string | null;
+ /** Target Language */
+ target_language: string | null;
+ /** Reviewed */
+ reviewed: boolean;
+ /** Meeting Id */
+ meeting_id: string | null;
+ source_kind: components["schemas"]["SourceKind"];
+ /** Room Id */
+ room_id?: string | null;
+ /** Room Name */
+ room_name?: string | null;
+ /** Audio Deleted */
+ audio_deleted?: boolean | null;
+ /** Participants */
+ participants: components["schemas"]["TranscriptParticipant"][] | null;
+ };
+ /** GetTranscriptMinimal */
+ GetTranscriptMinimal: {
+ /** Id */
+ id: string;
+ /** User Id */
+ user_id: string | null;
+ /** Name */
+ name: string;
+ /**
+ * Status
+ * @enum {string}
+ */
+ status:
+ | "idle"
+ | "uploaded"
+ | "recording"
+ | "processing"
+ | "error"
+ | "ended";
+ /** Locked */
+ locked: boolean;
+ /** Duration */
+ duration: number;
+ /** Title */
+ title: string | null;
+ /** Short Summary */
+ short_summary: string | null;
+ /** Long Summary */
+ long_summary: string | null;
+ /** Created At */
+ created_at: string;
+ /**
+ * Share Mode
+ * @default private
+ */
+ share_mode: string;
+ /** Source Language */
+ source_language: string | null;
+ /** Target Language */
+ target_language: string | null;
+ /** Reviewed */
+ reviewed: boolean;
+ /** Meeting Id */
+ meeting_id: string | null;
+ source_kind: components["schemas"]["SourceKind"];
+ /** Room Id */
+ room_id?: string | null;
+ /** Room Name */
+ room_name?: string | null;
+ /** Audio Deleted */
+ audio_deleted?: boolean | null;
+ };
+ /** GetTranscriptSegmentTopic */
+ GetTranscriptSegmentTopic: {
+ /** Text */
+ text: string;
+ /** Start */
+ start: number;
+ /** Speaker */
+ speaker: number;
+ };
+ /** GetTranscriptTopic */
+ GetTranscriptTopic: {
+ /** Id */
+ id: string;
+ /** Title */
+ title: string;
+ /** Summary */
+ summary: string;
+ /** Timestamp */
+ timestamp: number;
+ /** Duration */
+ duration: number | null;
+ /** Transcript */
+ transcript: string;
+ /**
+ * Segments
+ * @default []
+ */
+ segments: components["schemas"]["GetTranscriptSegmentTopic"][];
+ };
+ /** GetTranscriptTopicWithWords */
+ GetTranscriptTopicWithWords: {
+ /** Id */
+ id: string;
+ /** Title */
+ title: string;
+ /** Summary */
+ summary: string;
+ /** Timestamp */
+ timestamp: number;
+ /** Duration */
+ duration: number | null;
+ /** Transcript */
+ transcript: string;
+ /**
+ * Segments
+ * @default []
+ */
+ segments: components["schemas"]["GetTranscriptSegmentTopic"][];
+ /**
+ * Words
+ * @default []
+ */
+ words: components["schemas"]["Word"][];
+ };
+ /** GetTranscriptTopicWithWordsPerSpeaker */
+ GetTranscriptTopicWithWordsPerSpeaker: {
+ /** Id */
+ id: string;
+ /** Title */
+ title: string;
+ /** Summary */
+ summary: string;
+ /** Timestamp */
+ timestamp: number;
+ /** Duration */
+ duration: number | null;
+ /** Transcript */
+ transcript: string;
+ /**
+ * Segments
+ * @default []
+ */
+ segments: components["schemas"]["GetTranscriptSegmentTopic"][];
+ /**
+ * Words Per Speaker
+ * @default []
+ */
+ words_per_speaker: components["schemas"]["SpeakerWords"][];
+ };
+ /** HTTPValidationError */
+ HTTPValidationError: {
+ /** Detail */
+ detail?: components["schemas"]["ValidationError"][];
+ };
+ /** Meeting */
+ Meeting: {
+ /** Id */
+ id: string;
+ /** Room Name */
+ room_name: string;
+ /** Room Url */
+ room_url: string;
+ /** Host Room Url */
+ host_room_url: string;
+ /**
+ * Start Date
+ * Format: date-time
+ */
+ start_date: string;
+ /**
+ * End Date
+ * Format: date-time
+ */
+ end_date: string;
+ /**
+ * Recording Type
+ * @default cloud
+ * @enum {string}
+ */
+ recording_type: "none" | "local" | "cloud";
+ };
+ /** MeetingConsentRequest */
+ MeetingConsentRequest: {
+ /** Consent Given */
+ consent_given: boolean;
+ };
+ /** Page[GetTranscriptMinimal] */
+ Page_GetTranscriptMinimal_: {
+ /** Items */
+ items: components["schemas"]["GetTranscriptMinimal"][];
+ /** Total */
+ total?: number | null;
+ /** Page */
+ page: number | null;
+ /** Size */
+ size: number | null;
+ /** Pages */
+ pages?: number | null;
+ };
+ /** Page[RoomDetails] */
+ Page_RoomDetails_: {
+ /** Items */
+ items: components["schemas"]["RoomDetails"][];
+ /** Total */
+ total?: number | null;
+ /** Page */
+ page: number | null;
+ /** Size */
+ size: number | null;
+ /** Pages */
+ pages?: number | null;
+ };
+ /** Participant */
+ Participant: {
+ /** Id */
+ id: string;
+ /** Speaker */
+ speaker: number | null;
+ /** Name */
+ name: string;
+ };
+ /** Room */
+ Room: {
+ /** Id */
+ id: string;
+ /** Name */
+ name: string;
+ /** User Id */
+ user_id: string;
+ /**
+ * Created At
+ * Format: date-time
+ */
+ created_at: string;
+ /** Zulip Auto Post */
+ zulip_auto_post: boolean;
+ /** Zulip Stream */
+ zulip_stream: string;
+ /** Zulip Topic */
+ zulip_topic: string;
+ /** Is Locked */
+ is_locked: boolean;
+ /** Room Mode */
+ room_mode: string;
+ /** Recording Type */
+ recording_type: string;
+ /** Recording Trigger */
+ recording_trigger: string;
+ /** Is Shared */
+ is_shared: boolean;
+ };
+ /** RoomDetails */
+ RoomDetails: {
+ /** Id */
+ id: string;
+ /** Name */
+ name: string;
+ /** User Id */
+ user_id: string;
+ /**
+ * Created At
+ * Format: date-time
+ */
+ created_at: string;
+ /** Zulip Auto Post */
+ zulip_auto_post: boolean;
+ /** Zulip Stream */
+ zulip_stream: string;
+ /** Zulip Topic */
+ zulip_topic: string;
+ /** Is Locked */
+ is_locked: boolean;
+ /** Room Mode */
+ room_mode: string;
+ /** Recording Type */
+ recording_type: string;
+ /** Recording Trigger */
+ recording_trigger: string;
+ /** Is Shared */
+ is_shared: boolean;
+ /** Webhook Url */
+ webhook_url: string | null;
+ /** Webhook Secret */
+ webhook_secret: string | null;
+ };
+ /** RtcOffer */
+ RtcOffer: {
+ /** Sdp */
+ sdp: string;
+ /** Type */
+ type: string;
+ };
+ /** SearchResponse */
+ SearchResponse: {
+ /** Results */
+ results: components["schemas"]["SearchResult"][];
+ /**
+ * Total
+ * @description Total number of search results
+ */
+ total: number;
+ /** Query */
+ query?: string | null;
+ /**
+ * Limit
+ * @description Results per page
+ */
+ limit: number;
+ /**
+ * Offset
+ * @description Number of results to skip
+ */
+ offset: number;
+ };
+ /**
+ * SearchResult
+ * @description Public search result model with computed fields.
+ */
+ SearchResult: {
+ /** Id */
+ id: string;
+ /** Title */
+ title?: string | null;
+ /** User Id */
+ user_id?: string | null;
+ /** Room Id */
+ room_id?: string | null;
+ /** Room Name */
+ room_name?: string | null;
+ source_kind: components["schemas"]["SourceKind"];
+ /** Created At */
+ created_at: string;
+ /** Status */
+ status: string;
+ /** Rank */
+ rank: number;
+ /**
+ * Duration
+ * @description Duration in seconds
+ */
+ duration: number | null;
+ /**
+ * Search Snippets
+ * @description Text snippets around search matches
+ */
+ search_snippets: string[];
+ /**
+ * Total Match Count
+ * @description Total number of matches found in the transcript
+ * @default 0
+ */
+ total_match_count: number;
+ };
+ /**
+ * SourceKind
+ * @enum {string}
+ */
+ SourceKind: "room" | "live" | "file";
+ /** SpeakerAssignment */
+ SpeakerAssignment: {
+ /** Speaker */
+ speaker?: number | null;
+ /** Participant */
+ participant?: string | null;
+ /** Timestamp From */
+ timestamp_from: number;
+ /** Timestamp To */
+ timestamp_to: number;
+ };
+ /** SpeakerAssignmentStatus */
+ SpeakerAssignmentStatus: {
+ /** Status */
+ status: string;
+ };
+ /** SpeakerMerge */
+ SpeakerMerge: {
+ /** Speaker From */
+ speaker_from: number;
+ /** Speaker To */
+ speaker_to: number;
+ };
+ /** SpeakerWords */
+ SpeakerWords: {
+ /** Speaker */
+ speaker: number;
+ /** Words */
+ words: components["schemas"]["Word"][];
+ };
+ /** Stream */
+ Stream: {
+ /** Stream Id */
+ stream_id: number;
+ /** Name */
+ name: string;
+ };
+ /** Topic */
+ Topic: {
+ /** Name */
+ name: string;
+ };
+ /** TranscriptParticipant */
+ TranscriptParticipant: {
+ /** Id */
+ id?: string;
+ /** Speaker */
+ speaker: number | null;
+ /** Name */
+ name: string;
+ };
+ /** UpdateParticipant */
+ UpdateParticipant: {
+ /** Speaker */
+ speaker?: number | null;
+ /** Name */
+ name?: string | null;
+ };
+ /** UpdateRoom */
+ UpdateRoom: {
+ /** Name */
+ name: string;
+ /** Zulip Auto Post */
+ zulip_auto_post: boolean;
+ /** Zulip Stream */
+ zulip_stream: string;
+ /** Zulip Topic */
+ zulip_topic: string;
+ /** Is Locked */
+ is_locked: boolean;
+ /** Room Mode */
+ room_mode: string;
+ /** Recording Type */
+ recording_type: string;
+ /** Recording Trigger */
+ recording_trigger: string;
+ /** Is Shared */
+ is_shared: boolean;
+ /** Webhook Url */
+ webhook_url: string;
+ /** Webhook Secret */
+ webhook_secret: string;
+ };
+ /** UpdateTranscript */
+ UpdateTranscript: {
+ /** Name */
+ name?: string | null;
+ /** Locked */
+ locked?: boolean | null;
+ /** Title */
+ title?: string | null;
+ /** Short Summary */
+ short_summary?: string | null;
+ /** Long Summary */
+ long_summary?: string | null;
+ /** Share Mode */
+ share_mode?: ("public" | "semi-private" | "private") | null;
+ /** Participants */
+ participants?: components["schemas"]["TranscriptParticipant"][] | null;
+ /** Reviewed */
+ reviewed?: boolean | null;
+ /** Audio Deleted */
+ audio_deleted?: boolean | null;
+ };
+ /** UserInfo */
+ UserInfo: {
+ /** Sub */
+ sub: string;
+ /** Email */
+ email: string | null;
+ /** Email Verified */
+ email_verified: boolean | null;
+ };
+ /** ValidationError */
+ ValidationError: {
+ /** Location */
+ loc: (string | number)[];
+ /** Message */
+ msg: string;
+ /** Error Type */
+ type: string;
+ };
+ /** WebhookTestResult */
+ WebhookTestResult: {
+ /** Success */
+ success: boolean;
+ /**
+ * Message
+ * @default
+ */
+ message: string;
+ /**
+ * Error
+ * @default
+ */
+ error: string;
+ /** Status Code */
+ status_code?: number | null;
+ /** Response Preview */
+ response_preview?: string | null;
+ };
+ /** WherebyWebhookEvent */
+ WherebyWebhookEvent: {
+ /** Apiversion */
+ apiVersion: string;
+ /** Id */
+ id: string;
+ /**
+ * Createdat
+ * Format: date-time
+ */
+ createdAt: string;
+ /** Type */
+ type: string;
+ /** Data */
+ data: {
+ [key: string]: unknown;
+ };
+ };
+ /** Word */
+ Word: {
+ /** Text */
+ text: string;
+ /**
+ * Start
+ * @description Time in seconds with float part
+ */
+ start: number;
+ /**
+ * End
+ * @description Time in seconds with float part
+ */
+ end: number;
+ /**
+ * Speaker
+ * @default 0
+ */
+ speaker: number;
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record;
+export interface operations {
+ metrics: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ };
+ };
+ v1_meeting_audio_consent: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ meeting_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["MeetingConsentRequest"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Page size */
+ size?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Page_RoomDetails_"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_create: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["CreateRoom"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Room"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ room_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["RoomDetails"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_delete: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ room_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["DeletionStatus"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_update: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ room_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UpdateRoom"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["RoomDetails"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_create_meeting: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ room_name: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Meeting"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_rooms_test_webhook: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ room_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["WebhookTestResult"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcripts_list: {
+ parameters: {
+ query?: {
+ source_kind?: components["schemas"]["SourceKind"] | null;
+ room_id?: string | null;
+ search_term?: string | null;
+ /** @description Page number */
+ page?: number;
+ /** @description Page size */
+ size?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Page_GetTranscriptMinimal_"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcripts_create: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["CreateTranscript"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscript"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcripts_search: {
+ parameters: {
+ query: {
+ /** @description Search query text */
+ q: string;
+ /** @description Results per page */
+ limit?: number;
+ /** @description Number of results to skip */
+ offset?: number;
+ room_id?: string | null;
+ source_kind?: components["schemas"]["SourceKind"] | null;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SearchResponse"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscript"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_delete: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["DeletionStatus"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_update: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UpdateTranscript"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscript"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_topics: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscriptTopic"][];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_topics_with_words: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscriptTopicWithWords"][];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_topics_with_words_per_speaker: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ topic_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_post_to_zulip: {
+ parameters: {
+ query: {
+ stream: string;
+ topic: string;
+ include_topics: boolean;
+ };
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_audio_mp3: {
+ parameters: {
+ query?: {
+ token?: string | null;
+ };
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_head_audio_mp3: {
+ parameters: {
+ query?: {
+ token?: string | null;
+ };
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_audio_waveform: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AudioWaveform"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_participants: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Participant"][];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_add_participant: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["CreateParticipant"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Participant"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_participant: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ participant_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Participant"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_delete_participant: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ participant_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["DeletionStatus"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_update_participant: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ participant_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UpdateParticipant"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Participant"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_assign_speaker: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["SpeakerAssignment"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SpeakerAssignmentStatus"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_merge_speaker: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["SpeakerMerge"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SpeakerAssignmentStatus"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_record_upload: {
+ parameters: {
+ query: {
+ chunk_number: number;
+ total_chunks: number;
+ };
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "multipart/form-data": components["schemas"]["Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_get_websocket_events: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_record_webrtc: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["RtcOffer"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_transcript_process: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ transcript_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_user_me: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UserInfo"] | null;
+ };
+ };
+ };
+ };
+ v1_zulip_get_streams: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Stream"][];
+ };
+ };
+ };
+ };
+ v1_zulip_get_topics: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ stream_id: number;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Topic"][];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ v1_whereby_webhook: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["WherebyWebhookEvent"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": unknown;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+}
diff --git a/www/jest.config.js b/www/jest.config.js
new file mode 100644
index 00000000..d2f3247b
--- /dev/null
+++ b/www/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ preset: "ts-jest",
+ testEnvironment: "node",
+ roots: ["/app"],
+ testMatch: ["**/__tests__/**/*.test.ts"],
+ collectCoverage: true,
+ collectCoverageFrom: ["app/**/*.ts", "!app/**/*.d.ts"],
+};
diff --git a/www/middleware.ts b/www/middleware.ts
index 39145220..2b60d715 100644
--- a/www/middleware.ts
+++ b/www/middleware.ts
@@ -1,16 +1,7 @@
import { withAuth } from "next-auth/middleware";
import { getConfig } from "./app/lib/edgeConfig";
import { NextResponse } from "next/server";
-
-const LOGIN_REQUIRED_PAGES = [
- "/transcripts/[!new]",
- "/browse(.*)",
- "/rooms(.*)",
-];
-
-const PROTECTED_PAGES = new RegExp(
- LOGIN_REQUIRED_PAGES.map((page) => `^${page}$`).join("|"),
-);
+import { PROTECTED_PAGES } from "./app/lib/auth";
export const config = {
matcher: [
diff --git a/www/next.config.js b/www/next.config.js
index e37d5402..bbc3f710 100644
--- a/www/next.config.js
+++ b/www/next.config.js
@@ -2,6 +2,9 @@
const nextConfig = {
output: "standalone",
experimental: { esmExternals: "loose" },
+ env: {
+ IS_CI: process.env.IS_CI,
+ },
};
module.exports = nextConfig;
diff --git a/www/openapi-ts.config.ts b/www/openapi-ts.config.ts
deleted file mode 100644
index 9304b8f7..00000000
--- a/www/openapi-ts.config.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { defineConfig } from "@hey-api/openapi-ts";
-
-export default defineConfig({
- client: "axios",
- name: "OpenApi",
- input: "http://127.0.0.1:1250/openapi.json",
- output: {
- path: "./app/api",
- format: "prettier",
- },
- services: {
- asClass: true,
- },
-});
diff --git a/www/package.json b/www/package.json
index 482a29f6..b7511147 100644
--- a/www/package.json
+++ b/www/package.json
@@ -8,7 +8,8 @@
"start": "next start",
"lint": "next lint",
"format": "prettier --write .",
- "openapi": "openapi-ts"
+ "openapi": "openapi-typescript http://127.0.0.1:1250/openapi.json -o ./app/reflector-api.d.ts",
+ "test": "jest"
},
"dependencies": {
"@chakra-ui/react": "^3.24.2",
@@ -17,21 +18,24 @@
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@sentry/nextjs": "^7.77.0",
+ "@tanstack/react-query": "^5.85.9",
+ "@types/ioredis": "^5.0.0",
"@vercel/edge-config": "^0.4.1",
- "@vercel/kv": "^2.0.0",
"@whereby.com/browser-sdk": "^3.3.4",
"autoprefixer": "10.4.20",
"axios": "^1.8.2",
"eslint": "^9.33.0",
"eslint-config-next": "^14.2.31",
"fontawesome": "^5.6.3",
- "ioredis": "^5.4.1",
+ "ioredis": "^5.7.0",
"jest-worker": "^29.6.2",
"lucide-react": "^0.525.0",
"next": "^14.2.30",
"next-auth": "^4.24.7",
"next-themes": "^0.4.6",
"nuqs": "^2.4.3",
+ "openapi-fetch": "^0.14.0",
+ "openapi-react-query": "^0.5.0",
"postcss": "8.4.31",
"prop-types": "^15.8.1",
"react": "^18.2.0",
@@ -41,21 +45,24 @@
"react-markdown": "^9.0.0",
"react-qr-code": "^2.0.12",
"react-select-search": "^4.1.7",
- "redlock": "^5.0.0-beta.2",
"sass": "^1.63.6",
"simple-peer": "^9.11.1",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6",
- "wavesurfer.js": "^7.4.2"
+ "wavesurfer.js": "^7.4.2",
+ "zod": "^4.1.5"
},
"main": "index.js",
"repository": "https://github.com/Monadical-SAS/reflector-ui.git",
"author": "Andreas ",
"license": "All Rights Reserved",
"devDependencies": {
- "@hey-api/openapi-ts": "^0.48.0",
+ "@types/jest": "^30.0.0",
"@types/react": "18.2.20",
+ "jest": "^30.1.3",
+ "openapi-typescript": "^7.9.1",
"prettier": "^3.0.0",
+ "ts-jest": "^29.4.1",
"vercel": "^37.3.0"
},
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
diff --git a/www/pnpm-lock.yaml b/www/pnpm-lock.yaml
index 55aef9c8..14b42c55 100644
--- a/www/pnpm-lock.yaml
+++ b/www/pnpm-lock.yaml
@@ -24,13 +24,16 @@ importers:
version: 0.2.3(@fortawesome/fontawesome-svg-core@6.7.2)(react@18.3.1)
"@sentry/nextjs":
specifier: ^7.77.0
- version: 7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)
+ version: 7.120.4(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)
+ "@tanstack/react-query":
+ specifier: ^5.85.9
+ version: 5.85.9(react@18.3.1)
+ "@types/ioredis":
+ specifier: ^5.0.0
+ version: 5.0.0
"@vercel/edge-config":
specifier: ^0.4.1
version: 0.4.1
- "@vercel/kv":
- specifier: ^2.0.0
- version: 2.0.0
"@whereby.com/browser-sdk":
specifier: ^3.3.4
version: 3.13.1(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -50,7 +53,7 @@ importers:
specifier: ^5.6.3
version: 5.6.3
ioredis:
- specifier: ^5.4.1
+ specifier: ^5.7.0
version: 5.7.0
jest-worker:
specifier: ^29.6.2
@@ -60,16 +63,22 @@ importers:
version: 0.525.0(react@18.3.1)
next:
specifier: ^14.2.30
- version: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
+ version: 14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
next-auth:
specifier: ^4.24.7
- version: 4.24.11(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 4.24.11(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nuqs:
specifier: ^2.4.3
- version: 2.4.3(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)
+ version: 2.4.3(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)
+ openapi-fetch:
+ specifier: ^0.14.0
+ version: 0.14.0
+ openapi-react-query:
+ specifier: ^0.5.0
+ version: 0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0)
postcss:
specifier: 8.4.31
version: 8.4.31
@@ -97,9 +106,6 @@ importers:
react-select-search:
specifier: ^4.1.7
version: 4.1.8(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- redlock:
- specifier: ^5.0.0-beta.2
- version: 5.0.0-beta.2
sass:
specifier: ^1.63.6
version: 1.90.0
@@ -115,16 +121,28 @@ importers:
wavesurfer.js:
specifier: ^7.4.2
version: 7.10.1
+ zod:
+ specifier: ^4.1.5
+ version: 4.1.5
devDependencies:
- "@hey-api/openapi-ts":
- specifier: ^0.48.0
- version: 0.48.3(typescript@5.9.2)
+ "@types/jest":
+ specifier: ^30.0.0
+ version: 30.0.0
"@types/react":
specifier: 18.2.20
version: 18.2.20
+ jest:
+ specifier: ^30.1.3
+ version: 30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ openapi-typescript:
+ specifier: ^7.9.1
+ version: 7.9.1(typescript@5.9.2)
prettier:
specifier: ^3.0.0
version: 3.6.2
+ ts-jest:
+ specifier: ^29.4.1
+ version: 29.4.1(@babel/core@7.28.3)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)))(typescript@5.9.2)
vercel:
specifier: ^37.3.0
version: 37.14.0
@@ -137,12 +155,12 @@ packages:
}
engines: { node: ">=10" }
- "@apidevtools/json-schema-ref-parser@11.6.4":
+ "@ampproject/remapping@2.3.0":
resolution:
{
- integrity: sha512-9K6xOqeevacvweLGik6LnZCb1fBtCOSIWQs8d096XGeqoLKC33UVMGz9+77Gw44KvbH4pKcQPWo4ZpxkXYj05w==,
+ integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==,
}
- engines: { node: ">= 16" }
+ engines: { node: ">=6.0.0" }
"@ark-ui/react@5.18.2":
resolution:
@@ -160,6 +178,20 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@babel/compat-data@7.28.0":
+ resolution:
+ {
+ integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/core@7.28.3":
+ resolution:
+ {
+ integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
"@babel/generator@7.28.0":
resolution:
{
@@ -167,6 +199,20 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@babel/generator@7.28.3":
+ resolution:
+ {
+ integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-compilation-targets@7.27.2":
+ resolution:
+ {
+ integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
"@babel/helper-globals@7.28.0":
resolution:
{
@@ -181,6 +227,22 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@babel/helper-module-transforms@7.28.3":
+ resolution:
+ {
+ integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0
+
+ "@babel/helper-plugin-utils@7.27.1":
+ resolution:
+ {
+ integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==,
+ }
+ engines: { node: ">=6.9.0" }
+
"@babel/helper-string-parser@7.27.1":
resolution:
{
@@ -195,6 +257,20 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@babel/helper-validator-option@7.27.1":
+ resolution:
+ {
+ integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helpers@7.28.3":
+ resolution:
+ {
+ integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==,
+ }
+ engines: { node: ">=6.9.0" }
+
"@babel/parser@7.28.0":
resolution:
{
@@ -203,6 +279,156 @@ packages:
engines: { node: ">=6.0.0" }
hasBin: true
+ "@babel/parser@7.28.3":
+ resolution:
+ {
+ integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==,
+ }
+ engines: { node: ">=6.0.0" }
+ hasBin: true
+
+ "@babel/plugin-syntax-async-generators@7.8.4":
+ resolution:
+ {
+ integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-bigint@7.8.3":
+ resolution:
+ {
+ integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-class-properties@7.12.13":
+ resolution:
+ {
+ integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-class-static-block@7.14.5":
+ resolution:
+ {
+ integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-import-attributes@7.27.1":
+ resolution:
+ {
+ integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-import-meta@7.10.4":
+ resolution:
+ {
+ integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-json-strings@7.8.3":
+ resolution:
+ {
+ integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-jsx@7.27.1":
+ resolution:
+ {
+ integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-logical-assignment-operators@7.10.4":
+ resolution:
+ {
+ integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3":
+ resolution:
+ {
+ integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-numeric-separator@7.10.4":
+ resolution:
+ {
+ integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-object-rest-spread@7.8.3":
+ resolution:
+ {
+ integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-optional-catch-binding@7.8.3":
+ resolution:
+ {
+ integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-optional-chaining@7.8.3":
+ resolution:
+ {
+ integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-private-property-in-object@7.14.5":
+ resolution:
+ {
+ integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-top-level-await@7.14.5":
+ resolution:
+ {
+ integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-syntax-typescript@7.27.1":
+ resolution:
+ {
+ integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
"@babel/runtime@7.28.2":
resolution:
{
@@ -224,6 +450,13 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@babel/traverse@7.28.3":
+ resolution:
+ {
+ integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
"@babel/types@7.28.2":
resolution:
{
@@ -231,6 +464,12 @@ packages:
}
engines: { node: ">=6.9.0" }
+ "@bcoe/v8-coverage@0.2.3":
+ resolution:
+ {
+ integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==,
+ }
+
"@chakra-ui/react@3.24.2":
resolution:
{
@@ -516,16 +755,6 @@ packages:
"@fortawesome/fontawesome-svg-core": ~1 || ~6 || ~7
react: ^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0
- "@hey-api/openapi-ts@0.48.3":
- resolution:
- {
- integrity: sha512-R53Nr4Gicz77icS+RiH0fwHa9A0uFPtzsjC8SBaGwtOel5ZyxeBbayWE6HhE789hp3dok9pegwWncwwOrr4WFA==,
- }
- engines: { node: ^18.0.0 || >=20.0.0 }
- hasBin: true
- peerDependencies:
- typescript: ^5.x
-
"@humanfs/core@0.19.1":
resolution:
{
@@ -573,10 +802,10 @@ packages:
integrity: sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==,
}
- "@ioredis/commands@1.3.0":
+ "@ioredis/commands@1.3.1":
resolution:
{
- integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==,
+ integrity: sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==,
}
"@isaacs/cliui@8.0.2":
@@ -586,6 +815,107 @@ packages:
}
engines: { node: ">=12" }
+ "@istanbuljs/load-nyc-config@1.1.0":
+ resolution:
+ {
+ integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==,
+ }
+ engines: { node: ">=8" }
+
+ "@istanbuljs/schema@0.1.3":
+ resolution:
+ {
+ integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==,
+ }
+ engines: { node: ">=8" }
+
+ "@jest/console@30.1.2":
+ resolution:
+ {
+ integrity: sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/core@30.1.3":
+ resolution:
+ {
+ integrity: sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ "@jest/diff-sequences@30.0.1":
+ resolution:
+ {
+ integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/environment@30.1.2":
+ resolution:
+ {
+ integrity: sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/expect-utils@30.1.2":
+ resolution:
+ {
+ integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/expect@30.1.2":
+ resolution:
+ {
+ integrity: sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/fake-timers@30.1.2":
+ resolution:
+ {
+ integrity: sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/get-type@30.1.0":
+ resolution:
+ {
+ integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/globals@30.1.2":
+ resolution:
+ {
+ integrity: sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/pattern@30.0.1":
+ resolution:
+ {
+ integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/reporters@30.1.3":
+ resolution:
+ {
+ integrity: sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
"@jest/schemas@29.6.3":
resolution:
{
@@ -593,6 +923,48 @@ packages:
}
engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 }
+ "@jest/schemas@30.0.5":
+ resolution:
+ {
+ integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/snapshot-utils@30.1.2":
+ resolution:
+ {
+ integrity: sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/source-map@30.0.1":
+ resolution:
+ {
+ integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/test-result@30.1.3":
+ resolution:
+ {
+ integrity: sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/test-sequencer@30.1.3":
+ resolution:
+ {
+ integrity: sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ "@jest/transform@30.1.2":
+ resolution:
+ {
+ integrity: sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
"@jest/types@29.6.3":
resolution:
{
@@ -600,6 +972,13 @@ packages:
}
engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 }
+ "@jest/types@30.0.5":
+ resolution:
+ {
+ integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
"@jridgewell/gen-mapping@0.3.13":
resolution:
{
@@ -631,12 +1010,6 @@ packages:
integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==,
}
- "@jsdevtools/ono@7.1.3":
- resolution:
- {
- integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==,
- }
-
"@mapbox/node-pre-gyp@1.0.11":
resolution:
{
@@ -914,6 +1287,13 @@ packages:
}
engines: { node: ">=14" }
+ "@pkgr/core@0.2.9":
+ resolution:
+ {
+ integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==,
+ }
+ engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 }
+
"@radix-ui/primitive@1.1.3":
resolution:
{
@@ -1198,6 +1578,25 @@ packages:
integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==,
}
+ "@redocly/ajv@8.11.3":
+ resolution:
+ {
+ integrity: sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ==,
+ }
+
+ "@redocly/config@0.22.2":
+ resolution:
+ {
+ integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==,
+ }
+
+ "@redocly/openapi-core@1.34.5":
+ resolution:
+ {
+ integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==,
+ }
+ engines: { node: ">=18.17.0", npm: ">=9.5.0" }
+
"@reduxjs/toolkit@2.8.2":
resolution:
{
@@ -1382,6 +1781,24 @@ packages:
integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==,
}
+ "@sinclair/typebox@0.34.41":
+ resolution:
+ {
+ integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==,
+ }
+
+ "@sinonjs/commons@3.0.1":
+ resolution:
+ {
+ integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==,
+ }
+
+ "@sinonjs/fake-timers@13.0.5":
+ resolution:
+ {
+ integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==,
+ }
+
"@socket.io/component-emitter@3.1.2":
resolution:
{
@@ -1418,6 +1835,20 @@ packages:
integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==,
}
+ "@tanstack/query-core@5.85.9":
+ resolution:
+ {
+ integrity: sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ==,
+ }
+
+ "@tanstack/react-query@5.85.9":
+ resolution:
+ {
+ integrity: sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ==,
+ }
+ peerDependencies:
+ react: ^18 || ^19
+
"@tootallnate/once@2.0.0":
resolution:
{
@@ -1461,6 +1892,30 @@ packages:
integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==,
}
+ "@types/babel__core@7.20.5":
+ resolution:
+ {
+ integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==,
+ }
+
+ "@types/babel__generator@7.27.0":
+ resolution:
+ {
+ integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==,
+ }
+
+ "@types/babel__template@7.4.4":
+ resolution:
+ {
+ integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==,
+ }
+
+ "@types/babel__traverse@7.28.0":
+ resolution:
+ {
+ integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==,
+ }
+
"@types/debug@4.1.12":
resolution:
{
@@ -1491,6 +1946,13 @@ packages:
integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==,
}
+ "@types/ioredis@5.0.0":
+ resolution:
+ {
+ integrity: sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g==,
+ }
+ deprecated: This is a stub types definition. ioredis provides its own type definitions, so you do not need this installed.
+
"@types/istanbul-lib-coverage@2.0.6":
resolution:
{
@@ -1509,6 +1971,12 @@ packages:
integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==,
}
+ "@types/jest@30.0.0":
+ resolution:
+ {
+ integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==,
+ }
+
"@types/json-schema@7.0.15":
resolution:
{
@@ -1575,6 +2043,12 @@ packages:
integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==,
}
+ "@types/stack-utils@2.0.3":
+ resolution:
+ {
+ integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==,
+ }
+
"@types/ua-parser-js@0.7.39":
resolution:
{
@@ -1888,12 +2362,6 @@ packages:
cpu: [x64]
os: [win32]
- "@upstash/redis@1.35.3":
- resolution:
- {
- integrity: sha512-hSjv66NOuahW3MisRGlSgoszU2uONAY2l5Qo3Sae8OT3/Tng9K+2/cBRuyPBX8egwEGcNNCF9+r0V6grNnhL+w==,
- }
-
"@vercel/build-utils@8.4.12":
resolution:
{
@@ -1950,13 +2418,6 @@ packages:
integrity: sha512-IPAVaALuGAzt2apvTtBs5tB+8zZRzn/yG3AGp8dFyCsw/v5YOuk0Q5s8Z3fayLvJbFpjrKtqRNDZzVJBBU3MrQ==,
}
- "@vercel/kv@2.0.0":
- resolution:
- {
- integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==,
- }
- engines: { node: ">=14.6" }
-
"@vercel/next@4.3.18":
resolution:
{
@@ -2502,6 +2963,13 @@ packages:
}
engines: { node: ">= 6.0.0" }
+ agent-base@7.1.4:
+ resolution:
+ {
+ integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==,
+ }
+ engines: { node: ">= 14" }
+
ajv@6.12.6:
resolution:
{
@@ -2514,6 +2982,20 @@ packages:
integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==,
}
+ ansi-colors@4.1.3:
+ resolution:
+ {
+ integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==,
+ }
+ engines: { node: ">=6" }
+
+ ansi-escapes@4.3.2:
+ resolution:
+ {
+ integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==,
+ }
+ engines: { node: ">=8" }
+
ansi-regex@5.0.1:
resolution:
{
@@ -2535,6 +3017,13 @@ packages:
}
engines: { node: ">=8" }
+ ansi-styles@5.2.0:
+ resolution:
+ {
+ integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==,
+ }
+ engines: { node: ">=10" }
+
ansi-styles@6.2.1:
resolution:
{
@@ -2587,6 +3076,12 @@ packages:
integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==,
}
+ argparse@1.0.10:
+ resolution:
+ {
+ integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==,
+ }
+
argparse@2.0.1:
resolution:
{
@@ -2758,6 +3253,29 @@ packages:
}
engines: { node: ">= 0.4" }
+ babel-jest@30.1.2:
+ resolution:
+ {
+ integrity: sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ peerDependencies:
+ "@babel/core": ^7.11.0
+
+ babel-plugin-istanbul@7.0.0:
+ resolution:
+ {
+ integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==,
+ }
+ engines: { node: ">=12" }
+
+ babel-plugin-jest-hoist@30.0.1:
+ resolution:
+ {
+ integrity: sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
babel-plugin-macros@3.1.0:
resolution:
{
@@ -2765,6 +3283,23 @@ packages:
}
engines: { node: ">=10", npm: ">=6" }
+ babel-preset-current-node-syntax@1.2.0:
+ resolution:
+ {
+ integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==,
+ }
+ peerDependencies:
+ "@babel/core": ^7.0.0 || ^8.0.0-0
+
+ babel-preset-jest@30.0.1:
+ resolution:
+ {
+ integrity: sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ peerDependencies:
+ "@babel/core": ^7.11.0
+
bail@2.0.2:
resolution:
{
@@ -2823,6 +3358,19 @@ packages:
engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 }
hasBin: true
+ bs-logger@0.2.6:
+ resolution:
+ {
+ integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==,
+ }
+ engines: { node: ">= 6" }
+
+ bser@2.1.1:
+ resolution:
+ {
+ integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==,
+ }
+
btoa@1.2.1:
resolution:
{
@@ -2837,6 +3385,12 @@ packages:
integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==,
}
+ buffer-from@1.1.2:
+ resolution:
+ {
+ integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==,
+ }
+
buffer@6.0.3:
resolution:
{
@@ -2857,17 +3411,6 @@ packages:
}
engines: { node: ">= 0.8" }
- c12@1.11.1:
- resolution:
- {
- integrity: sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==,
- }
- peerDependencies:
- magicast: ^0.3.4
- peerDependenciesMeta:
- magicast:
- optional: true
-
call-bind-apply-helpers@1.0.2:
resolution:
{
@@ -2903,12 +3446,19 @@ packages:
}
engines: { node: ">= 6" }
- camelcase@8.0.0:
+ camelcase@5.3.1:
resolution:
{
- integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==,
+ integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==,
}
- engines: { node: ">=16" }
+ engines: { node: ">=6" }
+
+ camelcase@6.3.0:
+ resolution:
+ {
+ integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==,
+ }
+ engines: { node: ">=10" }
caniuse-lite@1.0.30001734:
resolution:
@@ -2936,6 +3486,19 @@ packages:
}
engines: { node: ">=10" }
+ change-case@5.4.4:
+ resolution:
+ {
+ integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==,
+ }
+
+ char-regex@1.0.2:
+ resolution:
+ {
+ integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==,
+ }
+ engines: { node: ">=10" }
+
character-entities-html4@2.1.0:
resolution:
{
@@ -3007,11 +3570,12 @@ packages:
}
engines: { node: ">=8" }
- citty@0.1.6:
+ ci-info@4.3.0:
resolution:
{
- integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==,
+ integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==,
}
+ engines: { node: ">=8" }
cjs-module-lexer@1.2.3:
resolution:
@@ -3019,6 +3583,12 @@ packages:
integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==,
}
+ cjs-module-lexer@2.1.0:
+ resolution:
+ {
+ integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==,
+ }
+
classnames@2.5.1:
resolution:
{
@@ -3031,6 +3601,13 @@ packages:
integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==,
}
+ cliui@8.0.1:
+ resolution:
+ {
+ integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==,
+ }
+ engines: { node: ">=12" }
+
clsx@2.1.1:
resolution:
{
@@ -3045,12 +3622,25 @@ packages:
}
engines: { node: ">=0.10.0" }
+ co@4.6.0:
+ resolution:
+ {
+ integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==,
+ }
+ engines: { iojs: ">= 1.0.0", node: ">= 0.12.0" }
+
code-block-writer@10.1.1:
resolution:
{
integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==,
}
+ collect-v8-coverage@1.0.2:
+ resolution:
+ {
+ integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==,
+ }
+
color-convert@2.0.1:
resolution:
{
@@ -3071,6 +3661,12 @@ packages:
}
hasBin: true
+ colorette@1.4.0:
+ resolution:
+ {
+ integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==,
+ }
+
combined-stream@1.0.8:
resolution:
{
@@ -3084,13 +3680,6 @@ packages:
integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==,
}
- commander@12.1.0:
- resolution:
- {
- integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==,
- }
- engines: { node: ">=18" }
-
commander@4.1.1:
resolution:
{
@@ -3110,19 +3699,6 @@ packages:
integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==,
}
- confbox@0.1.8:
- resolution:
- {
- integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==,
- }
-
- consola@3.4.2:
- resolution:
- {
- integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==,
- }
- engines: { node: ^14.18.0 || >=16.10.0 }
-
console-control-strings@1.1.0:
resolution:
{
@@ -3149,6 +3725,12 @@ packages:
integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==,
}
+ convert-source-map@2.0.0:
+ resolution:
+ {
+ integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==,
+ }
+
cookie@0.7.2:
resolution:
{
@@ -3270,12 +3852,30 @@ packages:
integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==,
}
+ dedent@1.7.0:
+ resolution:
+ {
+ integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==,
+ }
+ peerDependencies:
+ babel-plugin-macros: ^3.1.0
+ peerDependenciesMeta:
+ babel-plugin-macros:
+ optional: true
+
deep-is@0.1.4:
resolution:
{
integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==,
}
+ deepmerge@4.3.1:
+ resolution:
+ {
+ integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==,
+ }
+ engines: { node: ">=0.10.0" }
+
define-data-property@1.1.4:
resolution:
{
@@ -3290,12 +3890,6 @@ packages:
}
engines: { node: ">= 0.4" }
- defu@6.1.4:
- resolution:
- {
- integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==,
- }
-
delayed-stream@1.0.0:
resolution:
{
@@ -3330,12 +3924,6 @@ packages:
}
engines: { node: ">=6" }
- destr@2.0.5:
- resolution:
- {
- integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==,
- }
-
detect-europe-js@0.1.2:
resolution:
{
@@ -3357,6 +3945,13 @@ packages:
}
engines: { node: ">=8" }
+ detect-newline@3.1.0:
+ resolution:
+ {
+ integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==,
+ }
+ engines: { node: ">=8" }
+
detect-node-es@1.1.0:
resolution:
{
@@ -3413,13 +4008,6 @@ packages:
integrity: sha512-h7g5eduvnLwowJJPkcB5lNzo8vd/Hx4e3I4IOtLpX0qB2wBiuryGLNa61MeFre4b6gMaQIhegMIZ2I8rQCAJwQ==,
}
- dotenv@16.6.1:
- resolution:
- {
- integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==,
- }
- engines: { node: ">=12" }
-
dunder-proto@1.0.1:
resolution:
{
@@ -3447,6 +4035,13 @@ packages:
integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==,
}
+ emittery@0.13.1:
+ resolution:
+ {
+ integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==,
+ }
+ engines: { node: ">=12" }
+
emoji-regex@8.0.0:
resolution:
{
@@ -3753,6 +4348,13 @@ packages:
}
engines: { node: ">=6" }
+ escape-string-regexp@2.0.0:
+ resolution:
+ {
+ integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==,
+ }
+ engines: { node: ">=8" }
+
escape-string-regexp@4.0.0:
resolution:
{
@@ -3899,6 +4501,14 @@ packages:
}
engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 }
+ esprima@4.0.1:
+ resolution:
+ {
+ integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==,
+ }
+ engines: { node: ">=4" }
+ hasBin: true
+
esquery@1.6.0:
resolution:
{
@@ -3973,6 +4583,27 @@ packages:
}
engines: { node: ^8.12.0 || >=9.7.0 }
+ execa@5.1.1:
+ resolution:
+ {
+ integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==,
+ }
+ engines: { node: ">=10" }
+
+ exit-x@0.2.2:
+ resolution:
+ {
+ integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==,
+ }
+ engines: { node: ">= 0.8.0" }
+
+ expect@30.1.2:
+ resolution:
+ {
+ integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
extend@3.0.2:
resolution:
{
@@ -4022,6 +4653,12 @@ packages:
integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==,
}
+ fb-watchman@2.0.2:
+ resolution:
+ {
+ integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==,
+ }
+
fd-slicer@1.1.0:
resolution:
{
@@ -4065,6 +4702,13 @@ packages:
integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==,
}
+ find-up@4.1.0:
+ resolution:
+ {
+ integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==,
+ }
+ engines: { node: ">=8" }
+
find-up@5.0.0:
resolution:
{
@@ -4213,12 +4857,26 @@ packages:
}
engines: { node: ">= 4" }
+ gensync@1.0.0-beta.2:
+ resolution:
+ {
+ integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==,
+ }
+ engines: { node: ">=6.9.0" }
+
get-browser-rtc@1.1.0:
resolution:
{
integrity: sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==,
}
+ get-caller-file@2.0.5:
+ resolution:
+ {
+ integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==,
+ }
+ engines: { node: 6.* || 8.* || >= 10.* }
+
get-intrinsic@1.3.0:
resolution:
{
@@ -4233,6 +4891,13 @@ packages:
}
engines: { node: ">=6" }
+ get-package-type@0.1.0:
+ resolution:
+ {
+ integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==,
+ }
+ engines: { node: ">=8.0.0" }
+
get-proto@1.0.1:
resolution:
{
@@ -4247,6 +4912,13 @@ packages:
}
engines: { node: ">=8" }
+ get-stream@6.0.1:
+ resolution:
+ {
+ integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==,
+ }
+ engines: { node: ">=10" }
+
get-symbol-description@1.1.0:
resolution:
{
@@ -4260,13 +4932,6 @@ packages:
integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==,
}
- giget@1.2.5:
- resolution:
- {
- integrity: sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==,
- }
- hasBin: true
-
glob-parent@5.1.2:
resolution:
{
@@ -4437,6 +5102,12 @@ packages:
integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==,
}
+ html-escaper@2.0.2:
+ resolution:
+ {
+ integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==,
+ }
+
html-url-attributes@3.0.1:
resolution:
{
@@ -4464,6 +5135,13 @@ packages:
}
engines: { node: ">= 6" }
+ https-proxy-agent@7.0.6:
+ resolution:
+ {
+ integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==,
+ }
+ engines: { node: ">= 14" }
+
human-signals@1.1.1:
resolution:
{
@@ -4471,6 +5149,13 @@ packages:
}
engines: { node: ">=8.12.0" }
+ human-signals@2.1.0:
+ resolution:
+ {
+ integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==,
+ }
+ engines: { node: ">=10.17.0" }
+
hyperhtml-style@0.1.3:
resolution:
{
@@ -4529,6 +5214,14 @@ packages:
}
engines: { node: ">=6" }
+ import-local@3.2.0:
+ resolution:
+ {
+ integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==,
+ }
+ engines: { node: ">=8" }
+ hasBin: true
+
imurmurhash@0.1.4:
resolution:
{
@@ -4536,6 +5229,13 @@ packages:
}
engines: { node: ">=0.8.19" }
+ index-to-position@1.1.0:
+ resolution:
+ {
+ integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==,
+ }
+ engines: { node: ">=18" }
+
inflight@1.0.6:
resolution:
{
@@ -4709,6 +5409,13 @@ packages:
}
engines: { node: ">=8" }
+ is-generator-fn@2.1.0:
+ resolution:
+ {
+ integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==,
+ }
+ engines: { node: ">=6" }
+
is-generator-function@1.1.0:
resolution:
{
@@ -4864,6 +5571,41 @@ packages:
integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==,
}
+ istanbul-lib-coverage@3.2.2:
+ resolution:
+ {
+ integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==,
+ }
+ engines: { node: ">=8" }
+
+ istanbul-lib-instrument@6.0.3:
+ resolution:
+ {
+ integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==,
+ }
+ engines: { node: ">=10" }
+
+ istanbul-lib-report@3.0.1:
+ resolution:
+ {
+ integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==,
+ }
+ engines: { node: ">=10" }
+
+ istanbul-lib-source-maps@5.0.6:
+ resolution:
+ {
+ integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==,
+ }
+ engines: { node: ">=10" }
+
+ istanbul-reports@3.2.0:
+ resolution:
+ {
+ integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==,
+ }
+ engines: { node: ">=8" }
+
iterator.prototype@1.1.5:
resolution:
{
@@ -4884,6 +5626,168 @@ packages:
integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==,
}
+ jest-changed-files@30.0.5:
+ resolution:
+ {
+ integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-circus@30.1.3:
+ resolution:
+ {
+ integrity: sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-cli@30.1.3:
+ resolution:
+ {
+ integrity: sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest-config@30.1.3:
+ resolution:
+ {
+ integrity: sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ peerDependencies:
+ "@types/node": "*"
+ esbuild-register: ">=3.4.0"
+ ts-node: ">=9.0.0"
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ esbuild-register:
+ optional: true
+ ts-node:
+ optional: true
+
+ jest-diff@30.1.2:
+ resolution:
+ {
+ integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-docblock@30.0.1:
+ resolution:
+ {
+ integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-each@30.1.0:
+ resolution:
+ {
+ integrity: sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-environment-node@30.1.2:
+ resolution:
+ {
+ integrity: sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-haste-map@30.1.0:
+ resolution:
+ {
+ integrity: sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-leak-detector@30.1.0:
+ resolution:
+ {
+ integrity: sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-matcher-utils@30.1.2:
+ resolution:
+ {
+ integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-message-util@30.1.0:
+ resolution:
+ {
+ integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-mock@30.0.5:
+ resolution:
+ {
+ integrity: sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-pnp-resolver@1.2.3:
+ resolution:
+ {
+ integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==,
+ }
+ engines: { node: ">=6" }
+ peerDependencies:
+ jest-resolve: "*"
+ peerDependenciesMeta:
+ jest-resolve:
+ optional: true
+
+ jest-regex-util@30.0.1:
+ resolution:
+ {
+ integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-resolve-dependencies@30.1.3:
+ resolution:
+ {
+ integrity: sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-resolve@30.1.3:
+ resolution:
+ {
+ integrity: sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-runner@30.1.3:
+ resolution:
+ {
+ integrity: sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-runtime@30.1.3:
+ resolution:
+ {
+ integrity: sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-snapshot@30.1.2:
+ resolution:
+ {
+ integrity: sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
jest-util@29.7.0:
resolution:
{
@@ -4891,6 +5795,27 @@ packages:
}
engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 }
+ jest-util@30.0.5:
+ resolution:
+ {
+ integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-validate@30.1.0:
+ resolution:
+ {
+ integrity: sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest-watcher@30.1.3:
+ resolution:
+ {
+ integrity: sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
jest-worker@29.7.0:
resolution:
{
@@ -4898,6 +5823,26 @@ packages:
}
engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 }
+ jest-worker@30.1.0:
+ resolution:
+ {
+ integrity: sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
+ jest@30.1.3:
+ resolution:
+ {
+ integrity: sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
jiti@1.21.7:
resolution:
{
@@ -4911,12 +5856,26 @@ packages:
integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==,
}
+ js-levenshtein@1.1.6:
+ resolution:
+ {
+ integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==,
+ }
+ engines: { node: ">=0.10.0" }
+
js-tokens@4.0.0:
resolution:
{
integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==,
}
+ js-yaml@3.14.1:
+ resolution:
+ {
+ integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==,
+ }
+ hasBin: true
+
js-yaml@4.1.0:
resolution:
{
@@ -4981,6 +5940,14 @@ packages:
}
hasBin: true
+ json5@2.2.3:
+ resolution:
+ {
+ integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==,
+ }
+ engines: { node: ">=6" }
+ hasBin: true
+
jsonfile@4.0.0:
resolution:
{
@@ -5019,6 +5986,13 @@ packages:
}
engines: { node: ">=0.10" }
+ leven@3.1.0:
+ resolution:
+ {
+ integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==,
+ }
+ engines: { node: ">=6" }
+
levn@0.4.1:
resolution:
{
@@ -5057,6 +6031,13 @@ packages:
integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==,
}
+ locate-path@5.0.0:
+ resolution:
+ {
+ integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==,
+ }
+ engines: { node: ">=8" }
+
locate-path@6.0.0:
resolution:
{
@@ -5076,6 +6057,12 @@ packages:
integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==,
}
+ lodash.memoize@4.1.2:
+ resolution:
+ {
+ integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==,
+ }
+
lodash.merge@4.6.2:
resolution:
{
@@ -5101,6 +6088,12 @@ packages:
integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==,
}
+ lru-cache@5.1.1:
+ resolution:
+ {
+ integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==,
+ }
+
lru-cache@6.0.0:
resolution:
{
@@ -5130,12 +6123,25 @@ packages:
}
engines: { node: ">=8" }
+ make-dir@4.0.0:
+ resolution:
+ {
+ integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==,
+ }
+ engines: { node: ">=10" }
+
make-error@1.3.6:
resolution:
{
integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==,
}
+ makeerror@1.0.12:
+ resolution:
+ {
+ integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==,
+ }
+
math-intrinsics@1.1.0:
resolution:
{
@@ -5460,12 +6466,6 @@ packages:
engines: { node: ">=10" }
hasBin: true
- mlly@1.7.4:
- resolution:
- {
- integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==,
- }
-
mri@1.2.0:
resolution:
{
@@ -5566,24 +6566,12 @@ packages:
sass:
optional: true
- node-abort-controller@3.1.1:
- resolution:
- {
- integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==,
- }
-
node-addon-api@7.1.1:
resolution:
{
integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==,
}
- node-fetch-native@1.6.7:
- resolution:
- {
- integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==,
- }
-
node-fetch@2.6.7:
resolution:
{
@@ -5627,6 +6615,12 @@ packages:
}
hasBin: true
+ node-int64@0.4.0:
+ resolution:
+ {
+ integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==,
+ }
+
node-releases@2.0.19:
resolution:
{
@@ -5690,14 +6684,6 @@ packages:
react-router-dom:
optional: true
- nypm@0.5.4:
- resolution:
- {
- integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==,
- }
- engines: { node: ^14.16.0 || >=16.10.0 }
- hasBin: true
-
oauth@0.9.15:
resolution:
{
@@ -5774,12 +6760,6 @@ packages:
}
engines: { node: ">= 0.4" }
- ohash@1.1.6:
- resolution:
- {
- integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==,
- }
-
oidc-token-hash@5.1.1:
resolution:
{
@@ -5806,6 +6786,36 @@ packages:
}
engines: { node: ">=6" }
+ openapi-fetch@0.14.0:
+ resolution:
+ {
+ integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==,
+ }
+
+ openapi-react-query@0.5.0:
+ resolution:
+ {
+ integrity: sha512-VtyqiamsbWsdSWtXmj/fAR+m9nNxztsof6h8ZIsjRj8c8UR/x9AIwHwd60IqwgymmFwo7qfSJQ1ZzMJrtqjQVg==,
+ }
+ peerDependencies:
+ "@tanstack/react-query": ^5.25.0
+ openapi-fetch: ^0.14.0
+
+ openapi-typescript-helpers@0.0.15:
+ resolution:
+ {
+ integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==,
+ }
+
+ openapi-typescript@7.9.1:
+ resolution:
+ {
+ integrity: sha512-9gJtoY04mk6iPMbToPjPxEAtfXZ0dTsMZtsgUI8YZta0btPPig9DJFP4jlerQD/7QOwYgb0tl+zLUpDf7vb7VA==,
+ }
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.x
+
openid-client@5.7.1:
resolution:
{
@@ -5840,6 +6850,13 @@ packages:
}
engines: { node: ">=8" }
+ p-limit@2.3.0:
+ resolution:
+ {
+ integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==,
+ }
+ engines: { node: ">=6" }
+
p-limit@3.1.0:
resolution:
{
@@ -5847,6 +6864,13 @@ packages:
}
engines: { node: ">=10" }
+ p-locate@4.1.0:
+ resolution:
+ {
+ integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==,
+ }
+ engines: { node: ">=8" }
+
p-locate@5.0.0:
resolution:
{
@@ -5854,6 +6878,13 @@ packages:
}
engines: { node: ">=10" }
+ p-try@2.2.0:
+ resolution:
+ {
+ integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==,
+ }
+ engines: { node: ">=6" }
+
package-json-from-dist@1.0.1:
resolution:
{
@@ -5880,6 +6911,13 @@ packages:
}
engines: { node: ">=8" }
+ parse-json@8.3.0:
+ resolution:
+ {
+ integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==,
+ }
+ engines: { node: ">=18" }
+
parse-ms@2.1.0:
resolution:
{
@@ -5959,30 +6997,12 @@ packages:
}
engines: { node: ">=8" }
- pathe@1.1.2:
- resolution:
- {
- integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==,
- }
-
- pathe@2.0.3:
- resolution:
- {
- integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==,
- }
-
pend@1.2.0:
resolution:
{
integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==,
}
- perfect-debounce@1.0.0:
- resolution:
- {
- integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==,
- }
-
perfect-freehand@1.2.2:
resolution:
{
@@ -6029,11 +7049,19 @@ packages:
}
engines: { node: ">= 6" }
- pkg-types@1.3.1:
+ pkg-dir@4.2.0:
resolution:
{
- integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==,
+ integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==,
}
+ engines: { node: ">=8" }
+
+ pluralize@8.0.0:
+ resolution:
+ {
+ integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==,
+ }
+ engines: { node: ">=4" }
possible-typed-array-names@1.1.0:
resolution:
@@ -6146,6 +7174,13 @@ packages:
integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==,
}
+ pretty-format@30.0.5:
+ resolution:
+ {
+ integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==,
+ }
+ engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 }
+
pretty-ms@7.0.1:
resolution:
{
@@ -6209,6 +7244,12 @@ packages:
}
engines: { node: ">=6" }
+ pure-rand@7.0.1:
+ resolution:
+ {
+ integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==,
+ }
+
qr.js@0.0.0:
resolution:
{
@@ -6234,12 +7275,6 @@ packages:
}
engines: { node: ">= 0.8" }
- rc9@2.1.2:
- resolution:
- {
- integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==,
- }
-
react-dom@18.3.1:
resolution:
{
@@ -6271,6 +7306,12 @@ packages:
integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==,
}
+ react-is@18.3.1:
+ resolution:
+ {
+ integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==,
+ }
+
react-markdown@9.1.0:
resolution:
{
@@ -6392,13 +7433,6 @@ packages:
}
engines: { node: ">=4" }
- redlock@5.0.0-beta.2:
- resolution:
- {
- integrity: sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==,
- }
- engines: { node: ">=12" }
-
redux-thunk@3.1.0:
resolution:
{
@@ -6439,6 +7473,13 @@ packages:
integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==,
}
+ require-directory@2.1.1:
+ resolution:
+ {
+ integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==,
+ }
+ engines: { node: ">=0.10.0" }
+
require-from-string@2.0.2:
resolution:
{
@@ -6458,6 +7499,13 @@ packages:
integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==,
}
+ resolve-cwd@3.0.0:
+ resolution:
+ {
+ integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==,
+ }
+ engines: { node: ">=8" }
+
resolve-from@4.0.0:
resolution:
{
@@ -6727,6 +7775,13 @@ packages:
integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==,
}
+ slash@3.0.0:
+ resolution:
+ {
+ integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==,
+ }
+ engines: { node: ">=8" }
+
socket.io-client@4.7.2:
resolution:
{
@@ -6748,6 +7803,12 @@ packages:
}
engines: { node: ">=0.10.0" }
+ source-map-support@0.5.13:
+ resolution:
+ {
+ integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==,
+ }
+
source-map@0.5.7:
resolution:
{
@@ -6768,6 +7829,12 @@ packages:
integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==,
}
+ sprintf-js@1.0.3:
+ resolution:
+ {
+ integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==,
+ }
+
sprintf-js@1.1.3:
resolution:
{
@@ -6780,6 +7847,13 @@ packages:
integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==,
}
+ stack-utils@2.0.6:
+ resolution:
+ {
+ integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==,
+ }
+ engines: { node: ">=10" }
+
stacktrace-parser@0.1.11:
resolution:
{
@@ -6832,6 +7906,13 @@ packages:
}
engines: { node: ">=10.0.0" }
+ string-length@4.0.2:
+ resolution:
+ {
+ integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==,
+ }
+ engines: { node: ">=10" }
+
string-width@4.2.3:
resolution:
{
@@ -6920,6 +8001,13 @@ packages:
}
engines: { node: ">=4" }
+ strip-bom@4.0.0:
+ resolution:
+ {
+ integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==,
+ }
+ engines: { node: ">=8" }
+
strip-final-newline@2.0.0:
resolution:
{
@@ -6976,6 +8064,13 @@ packages:
engines: { node: ">=16 || 14 >=14.17" }
hasBin: true
+ supports-color@10.2.0:
+ resolution:
+ {
+ integrity: sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==,
+ }
+ engines: { node: ">=18" }
+
supports-color@7.2.0:
resolution:
{
@@ -7004,6 +8099,13 @@ packages:
}
engines: { node: ">= 0.4" }
+ synckit@0.11.11:
+ resolution:
+ {
+ integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==,
+ }
+ engines: { node: ^14.18.0 || >=16.0.0 }
+
tailwindcss@3.4.17:
resolution:
{
@@ -7026,6 +8128,13 @@ packages:
}
engines: { node: ">=10" }
+ test-exclude@6.0.0:
+ resolution:
+ {
+ integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==,
+ }
+ engines: { node: ">=8" }
+
thenify-all@1.6.0:
resolution:
{
@@ -7046,12 +8155,6 @@ packages:
}
engines: { node: ">=10" }
- tinyexec@0.3.2:
- resolution:
- {
- integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==,
- }
-
tinyglobby@0.2.14:
resolution:
{
@@ -7059,6 +8162,12 @@ packages:
}
engines: { node: ">=12.0.0" }
+ tmpl@1.0.5:
+ resolution:
+ {
+ integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==,
+ }
+
to-regex-range@5.0.1:
resolution:
{
@@ -7113,6 +8222,36 @@ packages:
integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==,
}
+ ts-jest@29.4.1:
+ resolution:
+ {
+ integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==,
+ }
+ engines: { node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0 }
+ hasBin: true
+ peerDependencies:
+ "@babel/core": ">=7.0.0-beta.0 <8"
+ "@jest/transform": ^29.0.0 || ^30.0.0
+ "@jest/types": ^29.0.0 || ^30.0.0
+ babel-jest: ^29.0.0 || ^30.0.0
+ esbuild: "*"
+ jest: ^29.0.0 || ^30.0.0
+ jest-util: ^29.0.0 || ^30.0.0
+ typescript: ">=4.3 <6"
+ peerDependenciesMeta:
+ "@babel/core":
+ optional: true
+ "@jest/transform":
+ optional: true
+ "@jest/types":
+ optional: true
+ babel-jest:
+ optional: true
+ esbuild:
+ optional: true
+ jest-util:
+ optional: true
+
ts-morph@12.0.0:
resolution:
{
@@ -7161,6 +8300,20 @@ packages:
}
engines: { node: ">= 0.8.0" }
+ type-detect@4.0.8:
+ resolution:
+ {
+ integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==,
+ }
+ engines: { node: ">=4" }
+
+ type-fest@0.21.3:
+ resolution:
+ {
+ integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==,
+ }
+ engines: { node: ">=10" }
+
type-fest@0.7.1:
resolution:
{
@@ -7168,6 +8321,13 @@ packages:
}
engines: { node: ">=8" }
+ type-fest@4.41.0:
+ resolution:
+ {
+ integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==,
+ }
+ engines: { node: ">=16" }
+
typed-array-buffer@1.0.3:
resolution:
{
@@ -7244,12 +8404,6 @@ packages:
integrity: sha512-v+Z8Jal+GtmKGtJ34GIQlCJAxrDt9kbjpNsNvYoAXFyr4gNfWlD4uJJuoNNu/0UTVaKvQwHaSU095YDl71lKPw==,
}
- ufo@1.6.1:
- resolution:
- {
- integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==,
- }
-
uglify-js@3.19.3:
resolution:
{
@@ -7289,12 +8443,6 @@ packages:
}
engines: { node: ">= 0.4" }
- uncrypto@0.1.3:
- resolution:
- {
- integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==,
- }
-
undici-types@7.10.0:
resolution:
{
@@ -7386,6 +8534,12 @@ packages:
integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==,
}
+ uri-js-replace@1.0.1:
+ resolution:
+ {
+ integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==,
+ }
+
uri-js@4.4.1:
resolution:
{
@@ -7464,6 +8618,13 @@ packages:
integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==,
}
+ v8-to-istanbul@9.3.0:
+ resolution:
+ {
+ integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==,
+ }
+ engines: { node: ">=10.12.0" }
+
vercel@37.14.0:
resolution:
{
@@ -7484,6 +8645,12 @@ packages:
integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==,
}
+ walker@1.0.8:
+ resolution:
+ {
+ integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==,
+ }
+
wavesurfer.js@7.10.1:
resolution:
{
@@ -7597,6 +8764,13 @@ packages:
integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==,
}
+ write-file-atomic@5.0.1:
+ resolution:
+ {
+ integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==,
+ }
+ engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 }
+
ws@8.17.1:
resolution:
{
@@ -7633,6 +8807,13 @@ packages:
}
engines: { node: ">=0.4.0" }
+ y18n@5.0.8:
+ resolution:
+ {
+ integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==,
+ }
+ engines: { node: ">=10" }
+
yallist@3.1.1:
resolution:
{
@@ -7645,6 +8826,12 @@ packages:
integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==,
}
+ yaml-ast-parser@0.0.43:
+ resolution:
+ {
+ integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==,
+ }
+
yaml@1.10.2:
resolution:
{
@@ -7660,6 +8847,20 @@ packages:
engines: { node: ">= 14.6" }
hasBin: true
+ yargs-parser@21.1.1:
+ resolution:
+ {
+ integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==,
+ }
+ engines: { node: ">=12" }
+
+ yargs@17.7.2:
+ resolution:
+ {
+ integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==,
+ }
+ engines: { node: ">=12" }
+
yauzl-clone@1.0.4:
resolution:
{
@@ -7694,6 +8895,12 @@ packages:
}
engines: { node: ">=10" }
+ zod@4.1.5:
+ resolution:
+ {
+ integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==,
+ }
+
zwitch@2.0.4:
resolution:
{
@@ -7703,11 +8910,10 @@ packages:
snapshots:
"@alloc/quick-lru@5.2.0": {}
- "@apidevtools/json-schema-ref-parser@11.6.4":
+ "@ampproject/remapping@2.3.0":
dependencies:
- "@jsdevtools/ono": 7.1.3
- "@types/json-schema": 7.0.15
- js-yaml: 4.1.0
+ "@jridgewell/gen-mapping": 0.3.13
+ "@jridgewell/trace-mapping": 0.3.30
"@ark-ui/react@5.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)":
dependencies:
@@ -7779,6 +8985,28 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ "@babel/compat-data@7.28.0": {}
+
+ "@babel/core@7.28.3":
+ dependencies:
+ "@ampproject/remapping": 2.3.0
+ "@babel/code-frame": 7.27.1
+ "@babel/generator": 7.28.3
+ "@babel/helper-compilation-targets": 7.27.2
+ "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3)
+ "@babel/helpers": 7.28.3
+ "@babel/parser": 7.28.3
+ "@babel/template": 7.27.2
+ "@babel/traverse": 7.28.3
+ "@babel/types": 7.28.2
+ convert-source-map: 2.0.0
+ debug: 4.4.1(supports-color@9.4.0)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
"@babel/generator@7.28.0":
dependencies:
"@babel/parser": 7.28.0
@@ -7787,6 +9015,22 @@ snapshots:
"@jridgewell/trace-mapping": 0.3.30
jsesc: 3.1.0
+ "@babel/generator@7.28.3":
+ dependencies:
+ "@babel/parser": 7.28.3
+ "@babel/types": 7.28.2
+ "@jridgewell/gen-mapping": 0.3.13
+ "@jridgewell/trace-mapping": 0.3.30
+ jsesc: 3.1.0
+
+ "@babel/helper-compilation-targets@7.27.2":
+ dependencies:
+ "@babel/compat-data": 7.28.0
+ "@babel/helper-validator-option": 7.27.1
+ browserslist: 4.25.2
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
"@babel/helper-globals@7.28.0": {}
"@babel/helper-module-imports@7.27.1":
@@ -7796,14 +9040,121 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ "@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-module-imports": 7.27.1
+ "@babel/helper-validator-identifier": 7.27.1
+ "@babel/traverse": 7.28.3
+ transitivePeerDependencies:
+ - supports-color
+
+ "@babel/helper-plugin-utils@7.27.1": {}
+
"@babel/helper-string-parser@7.27.1": {}
"@babel/helper-validator-identifier@7.27.1": {}
+ "@babel/helper-validator-option@7.27.1": {}
+
+ "@babel/helpers@7.28.3":
+ dependencies:
+ "@babel/template": 7.27.2
+ "@babel/types": 7.28.2
+
"@babel/parser@7.28.0":
dependencies:
"@babel/types": 7.28.2
+ "@babel/parser@7.28.3":
+ dependencies:
+ "@babel/types": 7.28.2
+
+ "@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/helper-plugin-utils": 7.27.1
+
"@babel/runtime@7.28.2": {}
"@babel/template@7.27.2":
@@ -7824,11 +9175,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ "@babel/traverse@7.28.3":
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ "@babel/generator": 7.28.3
+ "@babel/helper-globals": 7.28.0
+ "@babel/parser": 7.28.3
+ "@babel/template": 7.27.2
+ "@babel/types": 7.28.2
+ debug: 4.4.1(supports-color@9.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
"@babel/types@7.28.2":
dependencies:
"@babel/helper-string-parser": 7.27.1
"@babel/helper-validator-identifier": 7.27.1
+ "@bcoe/v8-coverage@0.2.3": {}
+
"@chakra-ui/react@3.24.2(@emotion/react@11.14.0(@types/react@18.2.20)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)":
dependencies:
"@ark-ui/react": 5.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -8027,17 +9392,6 @@ snapshots:
prop-types: 15.8.1
react: 18.3.1
- "@hey-api/openapi-ts@0.48.3(typescript@5.9.2)":
- dependencies:
- "@apidevtools/json-schema-ref-parser": 11.6.4
- c12: 1.11.1
- camelcase: 8.0.0
- commander: 12.1.0
- handlebars: 4.7.8
- typescript: 5.9.2
- transitivePeerDependencies:
- - magicast
-
"@humanfs/core@0.19.1": {}
"@humanfs/node@0.16.6":
@@ -8059,7 +9413,7 @@ snapshots:
dependencies:
"@swc/helpers": 0.5.17
- "@ioredis/commands@1.3.0": {}
+ "@ioredis/commands@1.3.1": {}
"@isaacs/cliui@8.0.2":
dependencies:
@@ -8070,10 +9424,189 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
+ "@istanbuljs/load-nyc-config@1.1.0":
+ dependencies:
+ camelcase: 5.3.1
+ find-up: 4.1.0
+ get-package-type: 0.1.0
+ js-yaml: 3.14.1
+ resolve-from: 5.0.0
+
+ "@istanbuljs/schema@0.1.3": {}
+
+ "@jest/console@30.1.2":
+ dependencies:
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ jest-message-util: 30.1.0
+ jest-util: 30.0.5
+ slash: 3.0.0
+
+ "@jest/core@30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))":
+ dependencies:
+ "@jest/console": 30.1.2
+ "@jest/pattern": 30.0.1
+ "@jest/reporters": 30.1.3
+ "@jest/test-result": 30.1.3
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 4.3.0
+ exit-x: 0.2.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 30.0.5
+ jest-config: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ jest-haste-map: 30.1.0
+ jest-message-util: 30.1.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.1.3
+ jest-resolve-dependencies: 30.1.3
+ jest-runner: 30.1.3
+ jest-runtime: 30.1.3
+ jest-snapshot: 30.1.2
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+ jest-watcher: 30.1.3
+ micromatch: 4.0.8
+ pretty-format: 30.0.5
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
+ "@jest/diff-sequences@30.0.1": {}
+
+ "@jest/environment@30.1.2":
+ dependencies:
+ "@jest/fake-timers": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ jest-mock: 30.0.5
+
+ "@jest/expect-utils@30.1.2":
+ dependencies:
+ "@jest/get-type": 30.1.0
+
+ "@jest/expect@30.1.2":
+ dependencies:
+ expect: 30.1.2
+ jest-snapshot: 30.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ "@jest/fake-timers@30.1.2":
+ dependencies:
+ "@jest/types": 30.0.5
+ "@sinonjs/fake-timers": 13.0.5
+ "@types/node": 24.2.1
+ jest-message-util: 30.1.0
+ jest-mock: 30.0.5
+ jest-util: 30.0.5
+
+ "@jest/get-type@30.1.0": {}
+
+ "@jest/globals@30.1.2":
+ dependencies:
+ "@jest/environment": 30.1.2
+ "@jest/expect": 30.1.2
+ "@jest/types": 30.0.5
+ jest-mock: 30.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ "@jest/pattern@30.0.1":
+ dependencies:
+ "@types/node": 24.2.1
+ jest-regex-util: 30.0.1
+
+ "@jest/reporters@30.1.3":
+ dependencies:
+ "@bcoe/v8-coverage": 0.2.3
+ "@jest/console": 30.1.2
+ "@jest/test-result": 30.1.3
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ "@jridgewell/trace-mapping": 0.3.30
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ collect-v8-coverage: 1.0.2
+ exit-x: 0.2.2
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-instrument: 6.0.3
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.2.0
+ jest-message-util: 30.1.0
+ jest-util: 30.0.5
+ jest-worker: 30.1.0
+ slash: 3.0.0
+ string-length: 4.0.2
+ v8-to-istanbul: 9.3.0
+ transitivePeerDependencies:
+ - supports-color
+
"@jest/schemas@29.6.3":
dependencies:
"@sinclair/typebox": 0.27.8
+ "@jest/schemas@30.0.5":
+ dependencies:
+ "@sinclair/typebox": 0.34.41
+
+ "@jest/snapshot-utils@30.1.2":
+ dependencies:
+ "@jest/types": 30.0.5
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ natural-compare: 1.4.0
+
+ "@jest/source-map@30.0.1":
+ dependencies:
+ "@jridgewell/trace-mapping": 0.3.30
+ callsites: 3.1.0
+ graceful-fs: 4.2.11
+
+ "@jest/test-result@30.1.3":
+ dependencies:
+ "@jest/console": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/istanbul-lib-coverage": 2.0.6
+ collect-v8-coverage: 1.0.2
+
+ "@jest/test-sequencer@30.1.3":
+ dependencies:
+ "@jest/test-result": 30.1.3
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.1.0
+ slash: 3.0.0
+
+ "@jest/transform@30.1.2":
+ dependencies:
+ "@babel/core": 7.28.3
+ "@jest/types": 30.0.5
+ "@jridgewell/trace-mapping": 0.3.30
+ babel-plugin-istanbul: 7.0.0
+ chalk: 4.1.2
+ convert-source-map: 2.0.0
+ fast-json-stable-stringify: 2.1.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.1.0
+ jest-regex-util: 30.0.1
+ jest-util: 30.0.5
+ micromatch: 4.0.8
+ pirates: 4.0.7
+ slash: 3.0.0
+ write-file-atomic: 5.0.1
+ transitivePeerDependencies:
+ - supports-color
+
"@jest/types@29.6.3":
dependencies:
"@jest/schemas": 29.6.3
@@ -8083,6 +9616,16 @@ snapshots:
"@types/yargs": 17.0.33
chalk: 4.1.2
+ "@jest/types@30.0.5":
+ dependencies:
+ "@jest/pattern": 30.0.1
+ "@jest/schemas": 30.0.5
+ "@types/istanbul-lib-coverage": 2.0.6
+ "@types/istanbul-reports": 3.0.4
+ "@types/node": 24.2.1
+ "@types/yargs": 17.0.33
+ chalk: 4.1.2
+
"@jridgewell/gen-mapping@0.3.13":
dependencies:
"@jridgewell/sourcemap-codec": 1.5.5
@@ -8102,8 +9645,6 @@ snapshots:
"@jridgewell/resolve-uri": 3.1.2
"@jridgewell/sourcemap-codec": 1.5.5
- "@jsdevtools/ono@7.1.3": {}
-
"@mapbox/node-pre-gyp@1.0.11":
dependencies:
detect-libc: 2.0.4
@@ -8241,6 +9782,8 @@ snapshots:
"@pkgjs/parseargs@0.11.0":
optional: true
+ "@pkgr/core@0.2.9": {}
+
"@radix-ui/primitive@1.1.3": {}
"@radix-ui/react-arrow@1.1.7(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)":
@@ -8420,6 +9963,29 @@ snapshots:
"@radix-ui/rect@1.1.1": {}
+ "@redocly/ajv@8.11.3":
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js-replace: 1.0.1
+
+ "@redocly/config@0.22.2": {}
+
+ "@redocly/openapi-core@1.34.5(supports-color@10.2.0)":
+ dependencies:
+ "@redocly/ajv": 8.11.3
+ "@redocly/config": 0.22.2
+ colorette: 1.4.0
+ https-proxy-agent: 7.0.6(supports-color@10.2.0)
+ js-levenshtein: 1.1.6
+ js-yaml: 4.1.0
+ minimatch: 5.1.6
+ pluralize: 8.0.0
+ yaml-ast-parser: 0.0.43
+ transitivePeerDependencies:
+ - supports-color
+
"@reduxjs/toolkit@2.8.2(react@18.3.1)":
dependencies:
"@standard-schema/spec": 1.0.0
@@ -8513,7 +10079,7 @@ snapshots:
"@sentry/utils": 7.120.4
localforage: 1.10.0
- "@sentry/nextjs@7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)":
+ "@sentry/nextjs@7.120.4(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)":
dependencies:
"@rollup/plugin-commonjs": 24.0.0(rollup@2.79.2)
"@sentry/core": 7.120.4
@@ -8525,7 +10091,7 @@ snapshots:
"@sentry/vercel-edge": 7.120.4
"@sentry/webpack-plugin": 1.21.0
chalk: 3.0.0
- next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
+ next: 14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
react: 18.3.1
resolve: 1.22.8
rollup: 2.79.2
@@ -8584,6 +10150,16 @@ snapshots:
"@sinclair/typebox@0.27.8": {}
+ "@sinclair/typebox@0.34.41": {}
+
+ "@sinonjs/commons@3.0.1":
+ dependencies:
+ type-detect: 4.0.8
+
+ "@sinonjs/fake-timers@13.0.5":
+ dependencies:
+ "@sinonjs/commons": 3.0.1
+
"@socket.io/component-emitter@3.1.2": {}
"@standard-schema/spec@1.0.0": {}
@@ -8601,6 +10177,13 @@ snapshots:
"@swc/counter": 0.1.3
tslib: 2.8.1
+ "@tanstack/query-core@5.85.9": {}
+
+ "@tanstack/react-query@5.85.9(react@18.3.1)":
+ dependencies:
+ "@tanstack/query-core": 5.85.9
+ react: 18.3.1
+
"@tootallnate/once@2.0.0": {}
"@ts-morph/common@0.11.1":
@@ -8623,6 +10206,27 @@ snapshots:
tslib: 2.8.1
optional: true
+ "@types/babel__core@7.20.5":
+ dependencies:
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+ "@types/babel__generator": 7.27.0
+ "@types/babel__template": 7.4.4
+ "@types/babel__traverse": 7.28.0
+
+ "@types/babel__generator@7.27.0":
+ dependencies:
+ "@babel/types": 7.28.2
+
+ "@types/babel__template@7.4.4":
+ dependencies:
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+
+ "@types/babel__traverse@7.28.0":
+ dependencies:
+ "@babel/types": 7.28.2
+
"@types/debug@4.1.12":
dependencies:
"@types/ms": 2.1.0
@@ -8639,6 +10243,12 @@ snapshots:
dependencies:
"@types/unist": 3.0.3
+ "@types/ioredis@5.0.0":
+ dependencies:
+ ioredis: 5.7.0
+ transitivePeerDependencies:
+ - supports-color
+
"@types/istanbul-lib-coverage@2.0.6": {}
"@types/istanbul-lib-report@3.0.3":
@@ -8649,6 +10259,11 @@ snapshots:
dependencies:
"@types/istanbul-lib-report": 3.0.3
+ "@types/jest@30.0.0":
+ dependencies:
+ expect: 30.1.2
+ pretty-format: 30.0.5
+
"@types/json-schema@7.0.15": {}
"@types/json5@0.0.29": {}
@@ -8682,6 +10297,8 @@ snapshots:
"@types/scheduler@0.26.0": {}
+ "@types/stack-utils@2.0.3": {}
+
"@types/ua-parser-js@0.7.39": {}
"@types/unist@2.0.11": {}
@@ -8860,10 +10477,6 @@ snapshots:
"@unrs/resolver-binding-win32-x64-msvc@1.11.1":
optional: true
- "@upstash/redis@1.35.3":
- dependencies:
- uncrypto: 0.1.3
-
"@vercel/build-utils@8.4.12": {}
"@vercel/edge-config-fs@0.1.0": {}
@@ -8920,10 +10533,6 @@ snapshots:
"@vercel/static-config": 3.0.0
ts-morph: 12.0.0
- "@vercel/kv@2.0.0":
- dependencies:
- "@upstash/redis": 1.35.3
-
"@vercel/next@4.3.18":
dependencies:
"@vercel/nft": 0.27.3
@@ -9601,6 +11210,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ agent-base@7.1.4: {}
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -9615,6 +11226,12 @@ snapshots:
require-from-string: 2.0.2
uri-js: 4.4.1
+ ansi-colors@4.1.3: {}
+
+ ansi-escapes@4.3.2:
+ dependencies:
+ type-fest: 0.21.3
+
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
@@ -9623,6 +11240,8 @@ snapshots:
dependencies:
color-convert: 2.0.1
+ ansi-styles@5.2.0: {}
+
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
@@ -9645,6 +11264,10 @@ snapshots:
arg@5.0.2: {}
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
argparse@2.0.1: {}
aria-hidden@1.2.6:
@@ -9771,12 +11394,66 @@ snapshots:
axobject-query@4.1.0: {}
+ babel-jest@30.1.2(@babel/core@7.28.3):
+ dependencies:
+ "@babel/core": 7.28.3
+ "@jest/transform": 30.1.2
+ "@types/babel__core": 7.20.5
+ babel-plugin-istanbul: 7.0.0
+ babel-preset-jest: 30.0.1(@babel/core@7.28.3)
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-istanbul@7.0.0:
+ dependencies:
+ "@babel/helper-plugin-utils": 7.27.1
+ "@istanbuljs/load-nyc-config": 1.1.0
+ "@istanbuljs/schema": 0.1.3
+ istanbul-lib-instrument: 6.0.3
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-jest-hoist@30.0.1:
+ dependencies:
+ "@babel/template": 7.27.2
+ "@babel/types": 7.28.2
+ "@types/babel__core": 7.20.5
+
babel-plugin-macros@3.1.0:
dependencies:
"@babel/runtime": 7.28.2
cosmiconfig: 7.1.0
resolve: 1.22.10
+ babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.3):
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/plugin-syntax-async-generators": 7.8.4(@babel/core@7.28.3)
+ "@babel/plugin-syntax-bigint": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-class-properties": 7.12.13(@babel/core@7.28.3)
+ "@babel/plugin-syntax-class-static-block": 7.14.5(@babel/core@7.28.3)
+ "@babel/plugin-syntax-import-attributes": 7.27.1(@babel/core@7.28.3)
+ "@babel/plugin-syntax-import-meta": 7.10.4(@babel/core@7.28.3)
+ "@babel/plugin-syntax-json-strings": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-logical-assignment-operators": 7.10.4(@babel/core@7.28.3)
+ "@babel/plugin-syntax-nullish-coalescing-operator": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-numeric-separator": 7.10.4(@babel/core@7.28.3)
+ "@babel/plugin-syntax-object-rest-spread": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-optional-catch-binding": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-optional-chaining": 7.8.3(@babel/core@7.28.3)
+ "@babel/plugin-syntax-private-property-in-object": 7.14.5(@babel/core@7.28.3)
+ "@babel/plugin-syntax-top-level-await": 7.14.5(@babel/core@7.28.3)
+
+ babel-preset-jest@30.0.1(@babel/core@7.28.3):
+ dependencies:
+ "@babel/core": 7.28.3
+ babel-plugin-jest-hoist: 30.0.1
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3)
+
bail@2.0.2: {}
balanced-match@1.0.2: {}
@@ -9809,10 +11486,20 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.2)
+ bs-logger@0.2.6:
+ dependencies:
+ fast-json-stable-stringify: 2.1.0
+
+ bser@2.1.1:
+ dependencies:
+ node-int64: 0.4.0
+
btoa@1.2.1: {}
buffer-crc32@0.2.13: {}
+ buffer-from@1.1.2: {}
+
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
@@ -9824,21 +11511,6 @@ snapshots:
bytes@3.1.0: {}
- c12@1.11.1:
- dependencies:
- chokidar: 3.6.0
- confbox: 0.1.8
- defu: 6.1.4
- dotenv: 16.6.1
- giget: 1.2.5
- jiti: 1.21.7
- mlly: 1.7.4
- ohash: 1.1.6
- pathe: 1.1.2
- perfect-debounce: 1.0.0
- pkg-types: 1.3.1
- rc9: 2.1.2
-
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -9860,7 +11532,9 @@ snapshots:
camelcase-css@2.0.1: {}
- camelcase@8.0.0: {}
+ camelcase@5.3.1: {}
+
+ camelcase@6.3.0: {}
caniuse-lite@1.0.30001734: {}
@@ -9876,6 +11550,10 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ change-case@5.4.4: {}
+
+ char-regex@1.0.2: {}
+
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
@@ -9922,22 +11600,32 @@ snapshots:
ci-info@3.9.0: {}
- citty@0.1.6:
- dependencies:
- consola: 3.4.2
+ ci-info@4.3.0: {}
cjs-module-lexer@1.2.3: {}
+ cjs-module-lexer@2.1.0: {}
+
classnames@2.5.1: {}
client-only@0.0.1: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
+ co@4.6.0: {}
+
code-block-writer@10.1.1: {}
+ collect-v8-coverage@1.0.2: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -9946,24 +11634,20 @@ snapshots:
color-support@1.1.3: {}
+ colorette@1.4.0: {}
+
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
comma-separated-tokens@2.0.3: {}
- commander@12.1.0: {}
-
commander@4.1.1: {}
commondir@1.0.1: {}
concat-map@0.0.1: {}
- confbox@0.1.8: {}
-
- consola@3.4.2: {}
-
console-control-strings@1.1.0: {}
content-type@1.0.4: {}
@@ -9972,6 +11656,8 @@ snapshots:
convert-source-map@1.9.0: {}
+ convert-source-map@2.0.0: {}
+
cookie@0.7.2: {}
cosmiconfig@7.1.0:
@@ -10026,6 +11712,12 @@ snapshots:
dependencies:
ms: 2.1.3
+ debug@4.4.1(supports-color@10.2.0):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 10.2.0
+
debug@4.4.1(supports-color@9.4.0):
dependencies:
ms: 2.1.3
@@ -10036,8 +11728,14 @@ snapshots:
dependencies:
character-entities: 2.0.2
+ dedent@1.7.0(babel-plugin-macros@3.1.0):
+ optionalDependencies:
+ babel-plugin-macros: 3.1.0
+
deep-is@0.1.4: {}
+ deepmerge@4.3.1: {}
+
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
@@ -10050,8 +11748,6 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
- defu@6.1.4: {}
-
delayed-stream@1.0.0: {}
delegates@1.0.0: {}
@@ -10062,8 +11758,6 @@ snapshots:
dequal@2.0.3: {}
- destr@2.0.5: {}
-
detect-europe-js@0.1.2: {}
detect-libc@1.0.3:
@@ -10071,6 +11765,8 @@ snapshots:
detect-libc@2.0.4: {}
+ detect-newline@3.1.0: {}
+
detect-node-es@1.1.0: {}
devlop@1.1.0:
@@ -10103,8 +11799,6 @@ snapshots:
domsanitizer: 0.2.3
umap: 1.0.2
- dotenv@16.6.1: {}
-
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -10127,6 +11821,8 @@ snapshots:
electron-to-chromium@1.5.200: {}
+ emittery@0.13.1: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -10347,6 +12043,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-string-regexp@2.0.0: {}
+
escape-string-regexp@4.0.0: {}
eslint-config-next@14.2.31(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2):
@@ -10534,6 +12232,8 @@ snapshots:
acorn-jsx: 5.3.2(acorn@8.15.0)
eslint-visitor-keys: 4.2.1
+ esprima@4.0.1: {}
+
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
@@ -10571,6 +12271,29 @@ snapshots:
signal-exit: 3.0.7
strip-final-newline: 2.0.0
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ exit-x@0.2.2: {}
+
+ expect@30.1.2:
+ dependencies:
+ "@jest/expect-utils": 30.1.2
+ "@jest/get-type": 30.1.0
+ jest-matcher-utils: 30.1.2
+ jest-message-util: 30.1.0
+ jest-mock: 30.0.5
+ jest-util: 30.0.5
+
extend@3.0.2: {}
fake-mediastreamtrack@1.2.0:
@@ -10598,6 +12321,10 @@ snapshots:
dependencies:
reusify: 1.1.0
+ fb-watchman@2.0.2:
+ dependencies:
+ bser: 2.1.1
+
fd-slicer@1.1.0:
dependencies:
pend: 1.2.0
@@ -10618,6 +12345,11 @@ snapshots:
find-root@1.1.0: {}
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -10708,8 +12440,12 @@ snapshots:
generic-pool@3.4.2: {}
+ gensync@1.0.0-beta.2: {}
+
get-browser-rtc@1.1.0: {}
+ get-caller-file@2.0.5: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -10725,6 +12461,8 @@ snapshots:
get-nonce@1.0.1: {}
+ get-package-type@0.1.0: {}
+
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
@@ -10734,6 +12472,8 @@ snapshots:
dependencies:
pump: 3.0.3
+ get-stream@6.0.1: {}
+
get-symbol-description@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -10744,16 +12484,6 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
- giget@1.2.5:
- dependencies:
- citty: 0.1.6
- consola: 3.4.2
- defu: 6.1.4
- node-fetch-native: 1.6.7
- nypm: 0.5.4
- pathe: 2.0.3
- tar: 6.2.1
-
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -10885,6 +12615,8 @@ snapshots:
dependencies:
react-is: 16.13.1
+ html-escaper@2.0.2: {}
+
html-url-attributes@3.0.1: {}
http-errors@1.4.0:
@@ -10907,8 +12639,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ https-proxy-agent@7.0.6(supports-color@10.2.0):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.1(supports-color@10.2.0)
+ transitivePeerDependencies:
+ - supports-color
+
human-signals@1.1.1: {}
+ human-signals@2.1.0: {}
+
hyperhtml-style@0.1.3: {}
iconv-lite@0.4.24:
@@ -10932,8 +12673,15 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
+ import-local@3.2.0:
+ dependencies:
+ pkg-dir: 4.2.0
+ resolve-cwd: 3.0.0
+
imurmurhash@0.1.4: {}
+ index-to-position@1.1.0: {}
+
inflight@1.0.6:
dependencies:
once: 1.4.0
@@ -10953,7 +12701,7 @@ snapshots:
ioredis@5.7.0:
dependencies:
- "@ioredis/commands": 1.3.0
+ "@ioredis/commands": 1.3.1
cluster-key-slot: 1.1.2
debug: 4.4.1(supports-color@9.4.0)
denque: 2.1.0
@@ -11043,6 +12791,8 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
+ is-generator-fn@2.1.0: {}
+
is-generator-function@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -11122,6 +12872,37 @@ snapshots:
isexe@2.0.0: {}
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-instrument@6.0.3:
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/parser": 7.28.0
+ "@istanbuljs/schema": 0.1.3
+ istanbul-lib-coverage: 3.2.2
+ semver: 7.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@5.0.6:
+ dependencies:
+ "@jridgewell/trace-mapping": 0.3.30
+ debug: 4.4.1(supports-color@9.4.0)
+ istanbul-lib-coverage: 3.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.2.0:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
iterator.prototype@1.1.5:
dependencies:
define-data-property: 1.1.4
@@ -11143,6 +12924,301 @@ snapshots:
optionalDependencies:
"@pkgjs/parseargs": 0.11.0
+ jest-changed-files@30.0.5:
+ dependencies:
+ execa: 5.1.1
+ jest-util: 30.0.5
+ p-limit: 3.1.0
+
+ jest-circus@30.1.3(babel-plugin-macros@3.1.0):
+ dependencies:
+ "@jest/environment": 30.1.2
+ "@jest/expect": 30.1.2
+ "@jest/test-result": 30.1.3
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ co: 4.6.0
+ dedent: 1.7.0(babel-plugin-macros@3.1.0)
+ is-generator-fn: 2.1.0
+ jest-each: 30.1.0
+ jest-matcher-utils: 30.1.2
+ jest-message-util: 30.1.0
+ jest-runtime: 30.1.3
+ jest-snapshot: 30.1.2
+ jest-util: 30.0.5
+ p-limit: 3.1.0
+ pretty-format: 30.0.5
+ pure-rand: 7.0.1
+ slash: 3.0.0
+ stack-utils: 2.0.6
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-cli@30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)):
+ dependencies:
+ "@jest/core": 30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ "@jest/test-result": 30.1.3
+ "@jest/types": 30.0.5
+ chalk: 4.1.2
+ exit-x: 0.2.2
+ import-local: 3.2.0
+ jest-config: 30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - "@types/node"
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
+ jest-config@30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)):
+ dependencies:
+ "@babel/core": 7.28.3
+ "@jest/get-type": 30.1.0
+ "@jest/pattern": 30.0.1
+ "@jest/test-sequencer": 30.1.3
+ "@jest/types": 30.0.5
+ babel-jest: 30.1.2(@babel/core@7.28.3)
+ chalk: 4.1.2
+ ci-info: 4.3.0
+ deepmerge: 4.3.1
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ jest-circus: 30.1.3(babel-plugin-macros@3.1.0)
+ jest-docblock: 30.0.1
+ jest-environment-node: 30.1.2
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.1.3
+ jest-runner: 30.1.3
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 30.0.5
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ "@types/node": 16.18.11
+ ts-node: 10.9.1(@types/node@16.18.11)(typescript@5.9.2)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)):
+ dependencies:
+ "@babel/core": 7.28.3
+ "@jest/get-type": 30.1.0
+ "@jest/pattern": 30.0.1
+ "@jest/test-sequencer": 30.1.3
+ "@jest/types": 30.0.5
+ babel-jest: 30.1.2(@babel/core@7.28.3)
+ chalk: 4.1.2
+ ci-info: 4.3.0
+ deepmerge: 4.3.1
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ jest-circus: 30.1.3(babel-plugin-macros@3.1.0)
+ jest-docblock: 30.0.1
+ jest-environment-node: 30.1.2
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.1.3
+ jest-runner: 30.1.3
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 30.0.5
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ "@types/node": 24.2.1
+ ts-node: 10.9.1(@types/node@16.18.11)(typescript@5.9.2)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-diff@30.1.2:
+ dependencies:
+ "@jest/diff-sequences": 30.0.1
+ "@jest/get-type": 30.1.0
+ chalk: 4.1.2
+ pretty-format: 30.0.5
+
+ jest-docblock@30.0.1:
+ dependencies:
+ detect-newline: 3.1.0
+
+ jest-each@30.1.0:
+ dependencies:
+ "@jest/get-type": 30.1.0
+ "@jest/types": 30.0.5
+ chalk: 4.1.2
+ jest-util: 30.0.5
+ pretty-format: 30.0.5
+
+ jest-environment-node@30.1.2:
+ dependencies:
+ "@jest/environment": 30.1.2
+ "@jest/fake-timers": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ jest-mock: 30.0.5
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+
+ jest-haste-map@30.1.0:
+ dependencies:
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ anymatch: 3.1.3
+ fb-watchman: 2.0.2
+ graceful-fs: 4.2.11
+ jest-regex-util: 30.0.1
+ jest-util: 30.0.5
+ jest-worker: 30.1.0
+ micromatch: 4.0.8
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-leak-detector@30.1.0:
+ dependencies:
+ "@jest/get-type": 30.1.0
+ pretty-format: 30.0.5
+
+ jest-matcher-utils@30.1.2:
+ dependencies:
+ "@jest/get-type": 30.1.0
+ chalk: 4.1.2
+ jest-diff: 30.1.2
+ pretty-format: 30.0.5
+
+ jest-message-util@30.1.0:
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ "@jest/types": 30.0.5
+ "@types/stack-utils": 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 30.0.5
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-mock@30.0.5:
+ dependencies:
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ jest-util: 30.0.5
+
+ jest-pnp-resolver@1.2.3(jest-resolve@30.1.3):
+ optionalDependencies:
+ jest-resolve: 30.1.3
+
+ jest-regex-util@30.0.1: {}
+
+ jest-resolve-dependencies@30.1.3:
+ dependencies:
+ jest-regex-util: 30.0.1
+ jest-snapshot: 30.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-resolve@30.1.3:
+ dependencies:
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.1.0
+ jest-pnp-resolver: 1.2.3(jest-resolve@30.1.3)
+ jest-util: 30.0.5
+ jest-validate: 30.1.0
+ slash: 3.0.0
+ unrs-resolver: 1.11.1
+
+ jest-runner@30.1.3:
+ dependencies:
+ "@jest/console": 30.1.2
+ "@jest/environment": 30.1.2
+ "@jest/test-result": 30.1.3
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ emittery: 0.13.1
+ exit-x: 0.2.2
+ graceful-fs: 4.2.11
+ jest-docblock: 30.0.1
+ jest-environment-node: 30.1.2
+ jest-haste-map: 30.1.0
+ jest-leak-detector: 30.1.0
+ jest-message-util: 30.1.0
+ jest-resolve: 30.1.3
+ jest-runtime: 30.1.3
+ jest-util: 30.0.5
+ jest-watcher: 30.1.3
+ jest-worker: 30.1.0
+ p-limit: 3.1.0
+ source-map-support: 0.5.13
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-runtime@30.1.3:
+ dependencies:
+ "@jest/environment": 30.1.2
+ "@jest/fake-timers": 30.1.2
+ "@jest/globals": 30.1.2
+ "@jest/source-map": 30.0.1
+ "@jest/test-result": 30.1.3
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ cjs-module-lexer: 2.1.0
+ collect-v8-coverage: 1.0.2
+ glob: 10.4.5
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.1.0
+ jest-message-util: 30.1.0
+ jest-mock: 30.0.5
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.1.3
+ jest-snapshot: 30.1.2
+ jest-util: 30.0.5
+ slash: 3.0.0
+ strip-bom: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-snapshot@30.1.2:
+ dependencies:
+ "@babel/core": 7.28.3
+ "@babel/generator": 7.28.0
+ "@babel/plugin-syntax-jsx": 7.27.1(@babel/core@7.28.3)
+ "@babel/plugin-syntax-typescript": 7.27.1(@babel/core@7.28.3)
+ "@babel/types": 7.28.2
+ "@jest/expect-utils": 30.1.2
+ "@jest/get-type": 30.1.0
+ "@jest/snapshot-utils": 30.1.2
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3)
+ chalk: 4.1.2
+ expect: 30.1.2
+ graceful-fs: 4.2.11
+ jest-diff: 30.1.2
+ jest-matcher-utils: 30.1.2
+ jest-message-util: 30.1.0
+ jest-util: 30.0.5
+ pretty-format: 30.0.5
+ semver: 7.7.2
+ synckit: 0.11.11
+ transitivePeerDependencies:
+ - supports-color
+
jest-util@29.7.0:
dependencies:
"@jest/types": 29.6.3
@@ -11152,6 +13228,35 @@ snapshots:
graceful-fs: 4.2.11
picomatch: 2.3.1
+ jest-util@30.0.5:
+ dependencies:
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ chalk: 4.1.2
+ ci-info: 4.3.0
+ graceful-fs: 4.2.11
+ picomatch: 4.0.3
+
+ jest-validate@30.1.0:
+ dependencies:
+ "@jest/get-type": 30.1.0
+ "@jest/types": 30.0.5
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ leven: 3.1.0
+ pretty-format: 30.0.5
+
+ jest-watcher@30.1.3:
+ dependencies:
+ "@jest/test-result": 30.1.3
+ "@jest/types": 30.0.5
+ "@types/node": 24.2.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ emittery: 0.13.1
+ jest-util: 30.0.5
+ string-length: 4.0.2
+
jest-worker@29.7.0:
dependencies:
"@types/node": 24.2.1
@@ -11159,12 +13264,40 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
+ jest-worker@30.1.0:
+ dependencies:
+ "@types/node": 24.2.1
+ "@ungap/structured-clone": 1.3.0
+ jest-util: 30.0.5
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest@30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)):
+ dependencies:
+ "@jest/core": 30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ "@jest/types": 30.0.5
+ import-local: 3.2.0
+ jest-cli: 30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ transitivePeerDependencies:
+ - "@types/node"
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+
jiti@1.21.7: {}
jose@4.15.9: {}
+ js-levenshtein@1.1.6: {}
+
js-tokens@4.0.0: {}
+ js-yaml@3.14.1:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@@ -11192,6 +13325,8 @@ snapshots:
dependencies:
minimist: 1.2.8
+ json5@2.2.3: {}
+
jsonfile@4.0.0:
optionalDependencies:
graceful-fs: 4.2.11
@@ -11219,6 +13354,8 @@ snapshots:
dependencies:
language-subtag-registry: 0.3.23
+ leven@3.1.0: {}
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -11248,6 +13385,10 @@ snapshots:
dependencies:
lie: 3.1.1
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@@ -11256,6 +13397,8 @@ snapshots:
lodash.isarguments@3.1.0: {}
+ lodash.memoize@4.1.2: {}
+
lodash.merge@4.6.2: {}
longest-streak@3.1.0: {}
@@ -11266,6 +13409,10 @@ snapshots:
lru-cache@10.4.3: {}
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
@@ -11282,8 +13429,16 @@ snapshots:
dependencies:
semver: 6.3.1
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.7.2
+
make-error@1.3.6: {}
+ makeerror@1.0.12:
+ dependencies:
+ tmpl: 1.0.5
+
math-intrinsics@1.1.0: {}
mdast-util-from-markdown@2.0.2:
@@ -11591,13 +13746,6 @@ snapshots:
mkdirp@1.0.4: {}
- mlly@1.7.4:
- dependencies:
- acorn: 8.15.0
- pathe: 2.0.3
- pkg-types: 1.3.1
- ufo: 1.6.1
-
mri@1.2.0: {}
ms@2.1.1: {}
@@ -11618,13 +13766,13 @@ snapshots:
neo-async@2.6.2: {}
- next-auth@4.24.11(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ next-auth@4.24.11(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
"@babel/runtime": 7.28.2
"@panva/hkdf": 1.2.1
cookie: 0.7.2
jose: 4.15.9
- next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
+ next: 14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
oauth: 0.9.15
openid-client: 5.7.1
preact: 10.27.0
@@ -11638,7 +13786,7 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0):
+ next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0):
dependencies:
"@next/env": 14.2.31
"@swc/helpers": 0.5.5
@@ -11648,7 +13796,7 @@ snapshots:
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- styled-jsx: 5.1.1(react@18.3.1)
+ styled-jsx: 5.1.1(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@18.3.1)
optionalDependencies:
"@next/swc-darwin-arm64": 14.2.31
"@next/swc-darwin-x64": 14.2.31
@@ -11664,13 +13812,9 @@ snapshots:
- "@babel/core"
- babel-plugin-macros
- node-abort-controller@3.1.1: {}
-
node-addon-api@7.1.1:
optional: true
- node-fetch-native@1.6.7: {}
-
node-fetch@2.6.7:
dependencies:
whatwg-url: 5.0.0
@@ -11685,6 +13829,8 @@ snapshots:
node-gyp-build@4.8.4: {}
+ node-int64@0.4.0: {}
+
node-releases@2.0.19: {}
nopt@5.0.0:
@@ -11706,21 +13852,12 @@ snapshots:
gauge: 3.0.2
set-blocking: 2.0.0
- nuqs@2.4.3(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1):
+ nuqs@2.4.3(next@14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1):
dependencies:
mitt: 3.0.1
react: 18.3.1
optionalDependencies:
- next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
-
- nypm@0.5.4:
- dependencies:
- citty: 0.1.6
- consola: 3.4.2
- pathe: 2.0.3
- pkg-types: 1.3.1
- tinyexec: 0.3.2
- ufo: 1.6.1
+ next: 14.2.31(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)
oauth@0.9.15: {}
@@ -11770,8 +13907,6 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
- ohash@1.1.6: {}
-
oidc-token-hash@5.1.1: {}
once@1.3.3:
@@ -11786,6 +13921,28 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
+ openapi-fetch@0.14.0:
+ dependencies:
+ openapi-typescript-helpers: 0.0.15
+
+ openapi-react-query@0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0):
+ dependencies:
+ "@tanstack/react-query": 5.85.9(react@18.3.1)
+ openapi-fetch: 0.14.0
+ openapi-typescript-helpers: 0.0.15
+
+ openapi-typescript-helpers@0.0.15: {}
+
+ openapi-typescript@7.9.1(typescript@5.9.2):
+ dependencies:
+ "@redocly/openapi-core": 1.34.5(supports-color@10.2.0)
+ ansi-colors: 4.1.3
+ change-case: 5.4.4
+ parse-json: 8.3.0
+ supports-color: 10.2.0
+ typescript: 5.9.2
+ yargs-parser: 21.1.1
+
openid-client@5.7.1:
dependencies:
jose: 4.15.9
@@ -11812,14 +13969,24 @@ snapshots:
p-finally@2.0.1: {}
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
+ p-try@2.2.0: {}
+
package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
@@ -11843,6 +14010,12 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
+ parse-json@8.3.0:
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ index-to-position: 1.1.0
+ type-fest: 4.41.0
+
parse-ms@2.1.0: {}
path-browserify@1.0.1: {}
@@ -11875,14 +14048,8 @@ snapshots:
path-type@4.0.0: {}
- pathe@1.1.2: {}
-
- pathe@2.0.3: {}
-
pend@1.2.0: {}
- perfect-debounce@1.0.0: {}
-
perfect-freehand@1.2.2: {}
picocolors@1.0.0: {}
@@ -11897,11 +14064,11 @@ snapshots:
pirates@4.0.7: {}
- pkg-types@1.3.1:
+ pkg-dir@4.2.0:
dependencies:
- confbox: 0.1.8
- mlly: 1.7.4
- pathe: 2.0.3
+ find-up: 4.1.0
+
+ pluralize@8.0.0: {}
possible-typed-array-names@1.1.0: {}
@@ -11962,6 +14129,12 @@ snapshots:
pretty-format@3.8.0: {}
+ pretty-format@30.0.5:
+ dependencies:
+ "@jest/schemas": 30.0.5
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
pretty-ms@7.0.1:
dependencies:
parse-ms: 2.1.0
@@ -11993,6 +14166,8 @@ snapshots:
punycode@2.3.1: {}
+ pure-rand@7.0.1: {}
+
qr.js@0.0.0: {}
queue-microtask@1.2.3: {}
@@ -12008,11 +14183,6 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
- rc9@2.1.2:
- dependencies:
- defu: 6.1.4
- destr: 2.0.5
-
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
@@ -12031,6 +14201,8 @@ snapshots:
react-is@16.13.1: {}
+ react-is@18.3.1: {}
+
react-markdown@9.1.0(@types/react@18.2.20)(react@18.3.1):
dependencies:
"@types/hast": 3.0.4
@@ -12118,10 +14290,6 @@ snapshots:
dependencies:
redis-errors: 1.2.0
- redlock@5.0.0-beta.2:
- dependencies:
- node-abort-controller: 3.1.1
-
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
@@ -12165,12 +14333,18 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ require-directory@2.1.1: {}
+
require-from-string@2.0.2: {}
reraf@1.1.1: {}
reselect@5.1.1: {}
+ resolve-cwd@3.0.0:
+ dependencies:
+ resolve-from: 5.0.0
+
resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
@@ -12339,6 +14513,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ slash@3.0.0: {}
+
socket.io-client@4.7.2:
dependencies:
"@socket.io/component-emitter": 3.1.2
@@ -12359,16 +14535,27 @@ snapshots:
source-map-js@1.2.1: {}
+ source-map-support@0.5.13:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
source-map@0.5.7: {}
source-map@0.6.1: {}
space-separated-tokens@2.0.2: {}
+ sprintf-js@1.0.3: {}
+
sprintf-js@1.1.3: {}
stable-hash@0.0.5: {}
+ stack-utils@2.0.6:
+ dependencies:
+ escape-string-regexp: 2.0.0
+
stacktrace-parser@0.1.11:
dependencies:
type-fest: 0.7.1
@@ -12396,6 +14583,11 @@ snapshots:
streamsearch@1.1.0: {}
+ string-length@4.0.2:
+ dependencies:
+ char-regex: 1.0.2
+ strip-ansi: 6.0.1
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -12477,6 +14669,8 @@ snapshots:
strip-bom@3.0.0: {}
+ strip-bom@4.0.0: {}
+
strip-final-newline@2.0.0: {}
strip-json-comments@3.1.1: {}
@@ -12489,10 +14683,13 @@ snapshots:
dependencies:
inline-style-parser: 0.2.4
- styled-jsx@5.1.1(react@18.3.1):
+ styled-jsx@5.1.1(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
+ optionalDependencies:
+ "@babel/core": 7.28.3
+ babel-plugin-macros: 3.1.0
stylis@4.2.0: {}
@@ -12506,6 +14703,8 @@ snapshots:
pirates: 4.0.7
ts-interface-checker: 0.1.13
+ supports-color@10.2.0: {}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@@ -12518,6 +14717,10 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ synckit@0.11.11:
+ dependencies:
+ "@pkgr/core": 0.2.9
+
tailwindcss@3.4.17(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)):
dependencies:
"@alloc/quick-lru": 5.2.0
@@ -12564,6 +14767,12 @@ snapshots:
mkdirp: 1.0.4
yallist: 4.0.0
+ test-exclude@6.0.0:
+ dependencies:
+ "@istanbuljs/schema": 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@@ -12576,13 +14785,13 @@ snapshots:
dependencies:
convert-hrtime: 3.0.0
- tinyexec@0.3.2: {}
-
tinyglobby@0.2.14:
dependencies:
fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3
+ tmpl@1.0.5: {}
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -12603,6 +14812,26 @@ snapshots:
ts-interface-checker@0.1.13: {}
+ ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)))(typescript@5.9.2):
+ dependencies:
+ bs-logger: 0.2.6
+ fast-json-stable-stringify: 2.1.0
+ handlebars: 4.7.8
+ jest: 30.1.3(@types/node@16.18.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2))
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
+ semver: 7.7.2
+ type-fest: 4.41.0
+ typescript: 5.9.2
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ "@babel/core": 7.28.3
+ "@jest/transform": 30.1.2
+ "@jest/types": 30.0.5
+ babel-jest: 30.1.2(@babel/core@7.28.3)
+ jest-util: 30.0.5
+
ts-morph@12.0.0:
dependencies:
"@ts-morph/common": 0.11.1
@@ -12660,8 +14889,14 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
+ type-detect@4.0.8: {}
+
+ type-fest@0.21.3: {}
+
type-fest@0.7.1: {}
+ type-fest@4.41.0: {}
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -12717,8 +14952,6 @@ snapshots:
udomdiff@1.1.2: {}
- ufo@1.6.1: {}
-
uglify-js@3.19.3:
optional: true
@@ -12739,8 +14972,6 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
- uncrypto@0.1.3: {}
-
undici-types@7.10.0: {}
undici@5.28.4:
@@ -12818,6 +15049,8 @@ snapshots:
uqr@0.1.2: {}
+ uri-js-replace@1.0.1: {}
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
@@ -12853,6 +15086,12 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
+ v8-to-istanbul@9.3.0:
+ dependencies:
+ "@jridgewell/trace-mapping": 0.3.30
+ "@types/istanbul-lib-coverage": 2.0.6
+ convert-source-map: 2.0.0
+
vercel@37.14.0:
dependencies:
"@vercel/build-utils": 8.4.12
@@ -12883,6 +15122,10 @@ snapshots:
"@types/unist": 3.0.3
vfile-message: 4.0.3
+ walker@1.0.8:
+ dependencies:
+ makeerror: 1.0.12
+
wavesurfer.js@7.10.1: {}
web-vitals@0.2.4: {}
@@ -12967,6 +15210,11 @@ snapshots:
wrappy@1.0.2: {}
+ write-file-atomic@5.0.1:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 4.1.0
+
ws@8.17.1: {}
xdg-app-paths@5.1.0:
@@ -12979,14 +15227,30 @@ snapshots:
xmlhttprequest-ssl@2.0.0: {}
+ y18n@5.0.8: {}
+
yallist@3.1.1: {}
yallist@4.0.0: {}
+ yaml-ast-parser@0.0.43: {}
+
yaml@1.10.2: {}
yaml@2.8.1: {}
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yauzl-clone@1.0.4:
dependencies:
events-intercept: 2.0.0
@@ -13005,4 +15269,6 @@ snapshots:
yocto-queue@0.1.0: {}
+ zod@4.1.5: {}
+
zwitch@2.0.4: {}
diff --git a/www/public/service-worker.js b/www/public/service-worker.js
index 109561d5..e798e369 100644
--- a/www/public/service-worker.js
+++ b/www/public/service-worker.js
@@ -1,4 +1,4 @@
-let authToken = ""; // Variable to store the token
+let authToken = null;
self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SET_AUTH_TOKEN") {