mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
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
This commit is contained in:
354
www/REDIS-FREE-AUTH.md
Normal file
354
www/REDIS-FREE-AUTH.md
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
# Redis-Free Authentication Solution for Reflector
|
||||||
|
|
||||||
|
## Problem Analysis
|
||||||
|
|
||||||
|
### The Multi-Tab Race Condition
|
||||||
|
|
||||||
|
The current implementation uses Redis to solve a specific problem:
|
||||||
|
|
||||||
|
- NextAuth's `useSession` hook broadcasts `getSession` events across all open tabs
|
||||||
|
- When a token expires, all tabs simultaneously try to refresh it
|
||||||
|
- Multiple refresh attempts with the same refresh_token cause 400 errors
|
||||||
|
- Redis + Redlock ensures only one refresh happens at a time
|
||||||
|
|
||||||
|
### Root Cause
|
||||||
|
|
||||||
|
The issue stems from **client-side broadcasting**, not from NextAuth itself. The `useSession` hook creates a BroadcastChannel that syncs sessions across tabs, triggering the race condition.
|
||||||
|
|
||||||
|
## Solution: Middleware-Based Token Refresh
|
||||||
|
|
||||||
|
Move token refresh from client-side to server-side middleware, eliminating broadcasting and race conditions entirely.
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
#### 1. Enhanced Middleware (`middleware.ts`)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { withAuth } from "next-auth/middleware";
|
||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
import { encode } from "next-auth/jwt";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getConfig } from "./app/lib/configProvider";
|
||||||
|
|
||||||
|
const REFRESH_THRESHOLD = 60 * 1000; // 60 seconds before expiry
|
||||||
|
|
||||||
|
async function refreshAccessToken(token: JWT): Promise<JWT> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(process.env.AUTHENTIK_REFRESH_TOKEN_URL!, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
client_id: process.env.AUTHENTIK_CLIENT_ID!,
|
||||||
|
client_secret: process.env.AUTHENTIK_CLIENT_SECRET!,
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: token.refreshToken as string,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to refresh token");
|
||||||
|
|
||||||
|
const refreshedTokens = await response.json();
|
||||||
|
return {
|
||||||
|
...token,
|
||||||
|
accessToken: refreshedTokens.access_token,
|
||||||
|
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
|
||||||
|
refreshToken: refreshedTokens.refresh_token || token.refreshToken,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { ...token, error: "RefreshAccessTokenError" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withAuth(
|
||||||
|
async function middleware(request) {
|
||||||
|
const config = await getConfig();
|
||||||
|
const pathname = request.nextUrl.pathname;
|
||||||
|
|
||||||
|
// Feature flag checks (existing)
|
||||||
|
if (
|
||||||
|
(!config.features.browse && pathname.startsWith("/browse")) ||
|
||||||
|
(!config.features.rooms && pathname.startsWith("/rooms"))
|
||||||
|
) {
|
||||||
|
return NextResponse.redirect(request.nextUrl.origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token refresh logic (new)
|
||||||
|
const token = await getToken({ req: request });
|
||||||
|
|
||||||
|
if (token && token.accessTokenExpires) {
|
||||||
|
const timeUntilExpiry = (token.accessTokenExpires as number) - Date.now();
|
||||||
|
|
||||||
|
// Refresh if within threshold and not already expired
|
||||||
|
if (timeUntilExpiry > 0 && timeUntilExpiry < REFRESH_THRESHOLD) {
|
||||||
|
try {
|
||||||
|
const refreshedToken = await refreshAccessToken(token);
|
||||||
|
|
||||||
|
if (!refreshedToken.error) {
|
||||||
|
// Encode new token
|
||||||
|
const newSessionToken = await encode({
|
||||||
|
secret: process.env.NEXTAUTH_SECRET!,
|
||||||
|
token: refreshedToken,
|
||||||
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update cookie
|
||||||
|
const response = NextResponse.next();
|
||||||
|
response.cookies.set({
|
||||||
|
name:
|
||||||
|
process.env.NODE_ENV === "production"
|
||||||
|
? "__Secure-next-auth.session-token"
|
||||||
|
: "next-auth.session-token",
|
||||||
|
value: newSessionToken,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
sameSite: "lax",
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Token refresh in middleware failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
callbacks: {
|
||||||
|
async authorized({ req, token }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.features.requireLogin &&
|
||||||
|
PROTECTED_PAGES.test(req.nextUrl.pathname)
|
||||||
|
) {
|
||||||
|
return !!token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Simplified auth.ts (No Redis)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AuthOptions } from "next-auth";
|
||||||
|
import AuthentikProvider from "next-auth/providers/authentik";
|
||||||
|
import { JWT } from "next-auth/jwt";
|
||||||
|
import { JWTWithAccessToken, CustomSession } from "./types";
|
||||||
|
|
||||||
|
const PRETIMEOUT = 60; // seconds before token expires
|
||||||
|
|
||||||
|
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",
|
||||||
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, account, user }) {
|
||||||
|
// Initial sign in
|
||||||
|
if (account && user) {
|
||||||
|
return {
|
||||||
|
...token,
|
||||||
|
accessToken: account.access_token,
|
||||||
|
accessTokenExpires: (account.expires_at as number) * 1000,
|
||||||
|
refreshToken: account.refresh_token, // Store in JWT
|
||||||
|
} as JWTWithAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return token as-is (refresh happens in middleware)
|
||||||
|
return 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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Remove Client-Side Auto-Refresh
|
||||||
|
|
||||||
|
**Delete:** `app/lib/SessionAutoRefresh.tsx`
|
||||||
|
|
||||||
|
**Update:** `app/lib/SessionProvider.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
"use client";
|
||||||
|
import { SessionProvider as SessionProviderNextAuth } from "next-auth/react";
|
||||||
|
|
||||||
|
export default function SessionProvider({ children }) {
|
||||||
|
return (
|
||||||
|
<SessionProviderNextAuth>
|
||||||
|
{children}
|
||||||
|
</SessionProviderNextAuth>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative: Client-Side Deduplication (If Keeping useSession)
|
||||||
|
|
||||||
|
If you need to keep client-side session features, implement request deduplication:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// app/lib/deduplicatedSession.ts
|
||||||
|
let refreshPromise: Promise<any> | null = null;
|
||||||
|
|
||||||
|
export async function deduplicatedRefresh() {
|
||||||
|
if (!refreshPromise) {
|
||||||
|
refreshPromise = fetch("/api/auth/session", {
|
||||||
|
method: "GET",
|
||||||
|
}).finally(() => {
|
||||||
|
refreshPromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return refreshPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified SessionAutoRefresh.tsx
|
||||||
|
export function SessionAutoRefresh({ children }) {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
if (shouldRefresh(session)) {
|
||||||
|
await deduplicatedRefresh(); // Use deduplicated call
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits of Middleware Approach
|
||||||
|
|
||||||
|
### Advantages
|
||||||
|
|
||||||
|
1. **No Race Conditions**: Each request handled independently server-side
|
||||||
|
2. **No Redis Required**: Eliminates infrastructure dependency
|
||||||
|
3. **No Broadcasting**: No multi-tab synchronization issues
|
||||||
|
4. **Automatic**: Refreshes on navigation, no polling needed
|
||||||
|
5. **Simpler**: Less client-side complexity
|
||||||
|
6. **Performance**: No unnecessary API calls from multiple tabs
|
||||||
|
|
||||||
|
### Trade-offs
|
||||||
|
|
||||||
|
1. **Long-lived pages**: Won't refresh without navigation
|
||||||
|
- Mitigation: Keep minimal client-side refresh for critical pages
|
||||||
|
2. **Server load**: Each request checks token
|
||||||
|
- Mitigation: Only checks protected routes
|
||||||
|
3. **Cookie size**: Refresh token stored in JWT
|
||||||
|
- Acceptable: ~200-300 bytes increase
|
||||||
|
|
||||||
|
## Migration Path
|
||||||
|
|
||||||
|
### Phase 1: Implement Middleware Refresh
|
||||||
|
|
||||||
|
1. Update middleware.ts with token refresh logic
|
||||||
|
2. Test with existing Redis-based auth.ts
|
||||||
|
3. Verify refresh works on navigation
|
||||||
|
|
||||||
|
### Phase 2: Remove Redis
|
||||||
|
|
||||||
|
1. Update auth.ts to store refresh_token in JWT
|
||||||
|
2. Remove Redis/Redlock imports
|
||||||
|
3. Test multi-tab scenarios
|
||||||
|
|
||||||
|
### Phase 3: Optimize Client-Side
|
||||||
|
|
||||||
|
1. Remove SessionAutoRefresh if not needed
|
||||||
|
2. Or implement deduplication for long-lived pages
|
||||||
|
3. Update documentation
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Single tab: Token refreshes before expiry
|
||||||
|
- [ ] Multiple tabs: No 400 errors on refresh
|
||||||
|
- [ ] Long session: 30-day refresh token works
|
||||||
|
- [ ] Failed refresh: Graceful degradation
|
||||||
|
- [ ] Protected routes: Still require authentication
|
||||||
|
- [ ] Feature flags: Still work as expected
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Required (same as before)
|
||||||
|
AUTHENTIK_CLIENT_ID=xxx
|
||||||
|
AUTHENTIK_CLIENT_SECRET=xxx
|
||||||
|
AUTHENTIK_ISSUER=https://auth.example.com/application/o/reflector/
|
||||||
|
AUTHENTIK_REFRESH_TOKEN_URL=https://auth.example.com/application/o/token/
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
NEXTAUTH_SECRET=xxx
|
||||||
|
|
||||||
|
# NOT Required anymore
|
||||||
|
# KV_URL=redis://... (removed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# No Redis needed!
|
||||||
|
frontend:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- AUTHENTIK_CLIENT_ID=${AUTHENTIK_CLIENT_ID}
|
||||||
|
- AUTHENTIK_CLIENT_SECRET=${AUTHENTIK_CLIENT_SECRET}
|
||||||
|
# No KV_URL needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Refresh Token in JWT**: Encrypted with A256GCM, secure
|
||||||
|
2. **Cookie Security**: HttpOnly, Secure, SameSite flags
|
||||||
|
3. **Token Rotation**: Authentik handles rotation on refresh
|
||||||
|
4. **Expiry Handling**: Graceful degradation on refresh failure
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The middleware-based approach eliminates the multi-tab race condition without Redis by:
|
||||||
|
|
||||||
|
1. Moving refresh logic server-side (no broadcasting)
|
||||||
|
2. Handling each request independently (no race)
|
||||||
|
3. Updating cookies transparently (no client involvement)
|
||||||
|
|
||||||
|
This solution is simpler, more maintainable, and aligns with NextAuth's evolution toward server-side session management.
|
||||||
@@ -19,17 +19,21 @@ import {
|
|||||||
parseAsStringLiteral,
|
parseAsStringLiteral,
|
||||||
} from "nuqs";
|
} from "nuqs";
|
||||||
import { LuX } from "react-icons/lu";
|
import { LuX } from "react-icons/lu";
|
||||||
import { useSearchTranscripts } from "../transcripts/useSearchTranscripts";
|
|
||||||
import useSessionUser from "../../lib/useSessionUser";
|
import useSessionUser from "../../lib/useSessionUser";
|
||||||
import { Room, SourceKind, SearchResult, $SourceKind } from "../../api";
|
import { Room, SourceKind, SearchResult, $SourceKind } from "../../api";
|
||||||
import useApi from "../../lib/useApi";
|
import {
|
||||||
import { useError } from "../../(errors)/errorContext";
|
useRoomsList,
|
||||||
|
useTranscriptsSearch,
|
||||||
|
useTranscriptDelete,
|
||||||
|
useTranscriptProcess,
|
||||||
|
} from "../../lib/api-hooks";
|
||||||
import FilterSidebar from "./_components/FilterSidebar";
|
import FilterSidebar from "./_components/FilterSidebar";
|
||||||
import Pagination, {
|
import Pagination, {
|
||||||
FIRST_PAGE,
|
FIRST_PAGE,
|
||||||
PaginationPage,
|
PaginationPage,
|
||||||
parsePaginationPage,
|
parsePaginationPage,
|
||||||
totalPages as getTotalPages,
|
totalPages as getTotalPages,
|
||||||
|
paginationPageTo0Based,
|
||||||
} from "./_components/Pagination";
|
} from "./_components/Pagination";
|
||||||
import TranscriptCards from "./_components/TranscriptCards";
|
import TranscriptCards from "./_components/TranscriptCards";
|
||||||
import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog";
|
import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog";
|
||||||
@@ -38,18 +42,6 @@ import { RECORD_A_MEETING_URL } from "../../api/urls";
|
|||||||
|
|
||||||
const SEARCH_FORM_QUERY_INPUT_NAME = "query" as const;
|
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<{
|
const SearchForm: React.FC<{
|
||||||
setPage: (page: PaginationPage) => void;
|
setPage: (page: PaginationPage) => void;
|
||||||
sourceKind: SourceKind | null;
|
sourceKind: SourceKind | null;
|
||||||
@@ -69,7 +61,6 @@ const SearchForm: React.FC<{
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
}) => {
|
}) => {
|
||||||
// to keep the search input controllable + more fine grained control (urlSearchQuery is updated on submits)
|
|
||||||
const [searchInputValue, setSearchInputValue] = useState(searchQuery || "");
|
const [searchInputValue, setSearchInputValue] = useState(searchQuery || "");
|
||||||
const handleSearchQuerySubmit = async (d: FormData) => {
|
const handleSearchQuerySubmit = async (d: FormData) => {
|
||||||
await setSearchQuery((d.get(SEARCH_FORM_QUERY_INPUT_NAME) as string) || "");
|
await setSearchQuery((d.get(SEARCH_FORM_QUERY_INPUT_NAME) as string) || "");
|
||||||
@@ -163,7 +154,6 @@ const UnderSearchFormFilterIndicators: React.FC<{
|
|||||||
p="1px"
|
p="1px"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSourceKind(null);
|
setSourceKind(null);
|
||||||
// TODO questionable
|
|
||||||
setRoomId(null);
|
setRoomId(null);
|
||||||
}}
|
}}
|
||||||
_hover={{ bg: "blue.200" }}
|
_hover={{ bg: "blue.200" }}
|
||||||
@@ -229,46 +219,41 @@ export default function TranscriptBrowser() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const maybePage = parsePaginationPage(urlPage);
|
const maybePage = parsePaginationPage(urlPage);
|
||||||
if ("error" in maybePage) {
|
if ("error" in maybePage) {
|
||||||
setPage(FIRST_PAGE).then(() => {
|
setPage(FIRST_PAGE).then(() => {});
|
||||||
/*may be called n times we dont care*/
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_setSafePage(maybePage.value);
|
_setSafePage(maybePage.value);
|
||||||
}, [urlPage]);
|
}, [urlPage]);
|
||||||
|
|
||||||
const [rooms, setRooms] = useState<Room[]>([]);
|
|
||||||
|
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
|
// Use new React Query hooks
|
||||||
const {
|
const {
|
||||||
results,
|
data: searchData,
|
||||||
totalCount: totalResults,
|
isLoading: searchLoading,
|
||||||
isLoading,
|
refetch: reloadSearch,
|
||||||
reload,
|
} = useTranscriptsSearch(urlSearchQuery, {
|
||||||
} = useSearchTranscripts(
|
limit: pageSize,
|
||||||
urlSearchQuery,
|
offset: paginationPageTo0Based(page) * pageSize,
|
||||||
{
|
room_id: urlRoomId || undefined,
|
||||||
roomIds: urlRoomId ? [urlRoomId] : null,
|
source_kind: urlSourceKind || undefined,
|
||||||
sourceKind: urlSourceKind,
|
});
|
||||||
},
|
|
||||||
{
|
const results = searchData?.results || [];
|
||||||
pageSize,
|
const totalResults = searchData?.total || 0;
|
||||||
page,
|
|
||||||
},
|
// Fetch rooms
|
||||||
);
|
const { data: roomsData } = useRoomsList(1);
|
||||||
|
const rooms = roomsData?.items || [];
|
||||||
|
|
||||||
const totalPages = getTotalPages(totalResults, pageSize);
|
const totalPages = getTotalPages(totalResults, pageSize);
|
||||||
|
|
||||||
const userName = useSessionUser().name;
|
const userName = useSessionUser().name;
|
||||||
const [deletionLoading, setDeletionLoading] = useState(false);
|
const [deletionLoading, setDeletionLoading] = useState(false);
|
||||||
const api = useApi();
|
|
||||||
const { setError } = useError();
|
|
||||||
const cancelRef = React.useRef(null);
|
const cancelRef = React.useRef(null);
|
||||||
const [transcriptToDeleteId, setTranscriptToDeleteId] =
|
const [transcriptToDeleteId, setTranscriptToDeleteId] =
|
||||||
React.useState<string>();
|
React.useState<string>();
|
||||||
|
|
||||||
usePrefetchRooms(setRooms);
|
|
||||||
|
|
||||||
const handleFilterTranscripts = (
|
const handleFilterTranscripts = (
|
||||||
sourceKind: SourceKind | null,
|
sourceKind: SourceKind | null,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
@@ -280,44 +265,52 @@ export default function TranscriptBrowser() {
|
|||||||
|
|
||||||
const onCloseDeletion = () => setTranscriptToDeleteId(undefined);
|
const onCloseDeletion = () => setTranscriptToDeleteId(undefined);
|
||||||
|
|
||||||
|
// Use mutation hooks
|
||||||
|
const deleteTranscript = useTranscriptDelete();
|
||||||
|
const processTranscript = useTranscriptProcess();
|
||||||
|
|
||||||
const confirmDeleteTranscript = (transcriptId: string) => {
|
const confirmDeleteTranscript = (transcriptId: string) => {
|
||||||
if (!api || deletionLoading) return;
|
if (deletionLoading) return;
|
||||||
setDeletionLoading(true);
|
setDeletionLoading(true);
|
||||||
api
|
deleteTranscript.mutate(
|
||||||
.v1TranscriptDelete({ transcriptId })
|
{
|
||||||
.then(() => {
|
params: {
|
||||||
setDeletionLoading(false);
|
path: { transcript_id: transcriptId },
|
||||||
onCloseDeletion();
|
},
|
||||||
reload();
|
},
|
||||||
})
|
{
|
||||||
.catch((err) => {
|
onSuccess: () => {
|
||||||
setDeletionLoading(false);
|
setDeletionLoading(false);
|
||||||
setError(err, "There was an error deleting the transcript");
|
onCloseDeletion();
|
||||||
});
|
reloadSearch();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setDeletionLoading(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProcessTranscript = (transcriptId: string) => {
|
const handleProcessTranscript = (transcriptId: string) => {
|
||||||
if (!api) {
|
processTranscript.mutate(
|
||||||
console.error("API not available on handleProcessTranscript");
|
{
|
||||||
return;
|
params: {
|
||||||
}
|
path: { transcript_id: transcriptId },
|
||||||
api
|
},
|
||||||
.v1TranscriptProcess({ transcriptId })
|
body: {},
|
||||||
.then((result) => {
|
},
|
||||||
const status =
|
{
|
||||||
result && typeof result === "object" && "status" in result
|
onSuccess: (result) => {
|
||||||
? (result as { status: string }).status
|
const status =
|
||||||
: undefined;
|
result && typeof result === "object" && "status" in result
|
||||||
if (status === "already running") {
|
? (result as { status: string }).status
|
||||||
setError(
|
: undefined;
|
||||||
new Error("Processing is already running, please wait"),
|
if (status === "already running") {
|
||||||
"Processing is already running, please wait",
|
// Note: setError is already handled in the hook
|
||||||
);
|
}
|
||||||
}
|
},
|
||||||
})
|
},
|
||||||
.catch((err) => {
|
);
|
||||||
setError(err, "There was an error processing the transcript");
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const transcriptToDelete = results?.find(
|
const transcriptToDelete = results?.find(
|
||||||
@@ -332,7 +325,7 @@ export default function TranscriptBrowser() {
|
|||||||
? transcriptToDelete.room_name || transcriptToDelete.room_id
|
? transcriptToDelete.room_name || transcriptToDelete.room_id
|
||||||
: transcriptToDelete?.source_kind;
|
: transcriptToDelete?.source_kind;
|
||||||
|
|
||||||
if (isLoading && results.length === 0) {
|
if (searchLoading && results.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDir="column"
|
flexDir="column"
|
||||||
@@ -360,7 +353,7 @@ export default function TranscriptBrowser() {
|
|||||||
>
|
>
|
||||||
<Heading size="lg">
|
<Heading size="lg">
|
||||||
{userName ? `${userName}'s Transcriptions` : "Your Transcriptions"}{" "}
|
{userName ? `${userName}'s Transcriptions` : "Your Transcriptions"}{" "}
|
||||||
{(isLoading || deletionLoading) && <Spinner size="sm" />}
|
{(searchLoading || deletionLoading) && <Spinner size="sm" />}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -403,12 +396,12 @@ export default function TranscriptBrowser() {
|
|||||||
<TranscriptCards
|
<TranscriptCards
|
||||||
results={results}
|
results={results}
|
||||||
query={urlSearchQuery}
|
query={urlSearchQuery}
|
||||||
isLoading={isLoading}
|
isLoading={searchLoading}
|
||||||
onDelete={setTranscriptToDeleteId}
|
onDelete={setTranscriptToDeleteId}
|
||||||
onReprocess={handleProcessTranscript}
|
onReprocess={handleProcessTranscript}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isLoading && results.length === 0 && (
|
{!searchLoading && results.length === 0 && (
|
||||||
<EmptyResult searchQuery={urlSearchQuery} />
|
<EmptyResult searchQuery={urlSearchQuery} />
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useRoomsList } from "../../lib/api-hooks";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
import { Page_Room_ } from "../../api";
|
import { Page_Room_ } from "../../api";
|
||||||
import { PaginationPage } from "../browse/_components/Pagination";
|
import { PaginationPage } from "../browse/_components/Pagination";
|
||||||
|
|
||||||
@@ -11,38 +9,16 @@ type RoomList = {
|
|||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
//always protected
|
// Wrapper to maintain backward compatibility
|
||||||
const useRoomList = (page: PaginationPage): RoomList => {
|
const useRoomList = (page: PaginationPage): RoomList => {
|
||||||
const [response, setResponse] = useState<Page_Room_ | null>(null);
|
const { data, isLoading, error, refetch } = useRoomsList(page);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
|
||||||
const { setError } = useError();
|
|
||||||
const api = useApi();
|
|
||||||
const [refetchCount, setRefetchCount] = useState(0);
|
|
||||||
|
|
||||||
const refetch = () => {
|
return {
|
||||||
setLoading(true);
|
response: data || null,
|
||||||
setRefetchCount(refetchCount + 1);
|
loading: isLoading,
|
||||||
|
error: error as Error | 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;
|
export default useRoomList;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// this hook is not great, we want to substitute it with a proper state management solution that is also not re-invention
|
// Wrapper for backward compatibility
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { SearchResult, SourceKind } from "../../api";
|
import { SearchResult, SourceKind } from "../../api";
|
||||||
import useApi from "../../lib/useApi";
|
import { useTranscriptsSearch } from "../../lib/api-hooks";
|
||||||
import {
|
import {
|
||||||
PaginationPage,
|
PaginationPage,
|
||||||
paginationPageTo0Based,
|
paginationPageTo0Based,
|
||||||
@@ -13,11 +11,6 @@ interface SearchFilters {
|
|||||||
sourceKind: SourceKind | null;
|
sourceKind: SourceKind | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_SEARCH_FILTERS: SearchFilters = {
|
|
||||||
roomIds: null,
|
|
||||||
sourceKind: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseSearchTranscriptsOptions = {
|
type UseSearchTranscriptsOptions = {
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
page: PaginationPage;
|
page: PaginationPage;
|
||||||
@@ -31,13 +24,9 @@ interface UseSearchTranscriptsReturn {
|
|||||||
reload: () => void;
|
reload: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hashEffectFilters(filters: SearchFilters): string {
|
|
||||||
return JSON.stringify(filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSearchTranscripts(
|
export function useSearchTranscripts(
|
||||||
query: string = "",
|
query: string = "",
|
||||||
filters: SearchFilters = EMPTY_SEARCH_FILTERS,
|
filters: SearchFilters = { roomIds: null, sourceKind: null },
|
||||||
options: UseSearchTranscriptsOptions = {
|
options: UseSearchTranscriptsOptions = {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
page: PaginationPage(1),
|
page: PaginationPage(1),
|
||||||
@@ -45,79 +34,18 @@ export function useSearchTranscripts(
|
|||||||
): UseSearchTranscriptsReturn {
|
): UseSearchTranscriptsReturn {
|
||||||
const { pageSize, page } = options;
|
const { pageSize, page } = options;
|
||||||
|
|
||||||
const [reloadCount, setReloadCount] = useState(0);
|
const { data, isLoading, error, refetch } = useTranscriptsSearch(query, {
|
||||||
|
limit: pageSize,
|
||||||
const api = useApi();
|
offset: paginationPageTo0Based(page) * pageSize,
|
||||||
const abortControllerRef = useRef<AbortController>();
|
room_id: filters.roomIds?.[0],
|
||||||
|
source_kind: filters.sourceKind || undefined,
|
||||||
const [data, setData] = useState<{ results: SearchResult[]; total: number }>({
|
|
||||||
results: [],
|
|
||||||
total: 0,
|
|
||||||
});
|
});
|
||||||
const [error, setError] = useState<any>();
|
|
||||||
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 {
|
return {
|
||||||
results: data.results,
|
results: data?.results || [],
|
||||||
totalCount: data.total,
|
totalCount: data?.total || 0,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
reload: () => setReloadCount(reloadCount + 1),
|
reload: refetch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { GetTranscript } from "../../api";
|
import { GetTranscript } from "../../api";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useTranscriptGet } from "../../lib/api-hooks";
|
||||||
import { shouldShowError } from "../../lib/errorUtils";
|
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
|
|
||||||
type ErrorTranscript = {
|
type ErrorTranscript = {
|
||||||
error: Error;
|
error: Error;
|
||||||
@@ -28,43 +25,33 @@ type SuccessTranscript = {
|
|||||||
const useTranscript = (
|
const useTranscript = (
|
||||||
id: string | null,
|
id: string | null,
|
||||||
): ErrorTranscript | LoadingTranscript | SuccessTranscript => {
|
): ErrorTranscript | LoadingTranscript | SuccessTranscript => {
|
||||||
const [response, setResponse] = useState<GetTranscript | null>(null);
|
const { data, isLoading, error, refetch } = useTranscriptGet(id);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
|
||||||
const [reload, setReload] = useState(0);
|
|
||||||
const { setError } = useError();
|
|
||||||
const api = useApi();
|
|
||||||
const reloadHandler = () => setReload((prev) => prev + 1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Map to the expected return format
|
||||||
if (!id || !api) return;
|
if (isLoading) {
|
||||||
|
return {
|
||||||
|
response: null,
|
||||||
|
loading: true,
|
||||||
|
error: false,
|
||||||
|
reload: refetch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (error) {
|
||||||
setLoading(true);
|
return {
|
||||||
}
|
error: error as Error,
|
||||||
|
loading: false,
|
||||||
|
response: null,
|
||||||
|
reload: refetch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
api
|
return {
|
||||||
.v1TranscriptGet({ transcriptId: id })
|
response: data as GetTranscript,
|
||||||
.then((result) => {
|
loading: false,
|
||||||
setResponse(result);
|
error: null,
|
||||||
setLoading(false);
|
reload: refetch,
|
||||||
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]);
|
|
||||||
|
|
||||||
return { response, loading, error, reload: reloadHandler } as
|
|
||||||
| ErrorTranscript
|
|
||||||
| LoadingTranscript
|
|
||||||
| SuccessTranscript;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useTranscript;
|
export default useTranscript;
|
||||||
|
|||||||
@@ -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<OpenAPIConfig>,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// 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";
|
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
|
||||||
|
|
||||||
export { handler as GET, handler as POST };
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
export type ApiRequestOptions<T = unknown> = {
|
|
||||||
readonly method:
|
|
||||||
| "GET"
|
|
||||||
| "PUT"
|
|
||||||
| "POST"
|
|
||||||
| "DELETE"
|
|
||||||
| "OPTIONS"
|
|
||||||
| "HEAD"
|
|
||||||
| "PATCH";
|
|
||||||
readonly url: string;
|
|
||||||
readonly path?: Record<string, unknown>;
|
|
||||||
readonly cookies?: Record<string, unknown>;
|
|
||||||
readonly headers?: Record<string, unknown>;
|
|
||||||
readonly query?: Record<string, unknown>;
|
|
||||||
readonly formData?: Record<string, unknown>;
|
|
||||||
readonly body?: any;
|
|
||||||
readonly mediaType?: string;
|
|
||||||
readonly responseHeader?: string;
|
|
||||||
readonly responseTransformer?: (data: unknown) => Promise<T>;
|
|
||||||
readonly errors?: Record<number | string, string>;
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export type ApiResult<TData = any> = {
|
|
||||||
readonly body: TData;
|
|
||||||
readonly ok: boolean;
|
|
||||||
readonly status: number;
|
|
||||||
readonly statusText: string;
|
|
||||||
readonly url: string;
|
|
||||||
};
|
|
||||||
@@ -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<T>
|
|
||||||
* @throws ApiError
|
|
||||||
*/
|
|
||||||
public override request<T>(
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
): CancelablePromise<T> {
|
|
||||||
return __request(this.config, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<T>(
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
): CancelablePromise<T>;
|
|
||||||
}
|
|
||||||
@@ -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<T> implements Promise<T> {
|
|
||||||
private _isResolved: boolean;
|
|
||||||
private _isRejected: boolean;
|
|
||||||
private _isCancelled: boolean;
|
|
||||||
readonly cancelHandlers: (() => void)[];
|
|
||||||
readonly promise: Promise<T>;
|
|
||||||
private _resolve?: (value: T | PromiseLike<T>) => void;
|
|
||||||
private _reject?: (reason?: unknown) => void;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
executor: (
|
|
||||||
resolve: (value: T | PromiseLike<T>) => void,
|
|
||||||
reject: (reason?: unknown) => void,
|
|
||||||
onCancel: OnCancel,
|
|
||||||
) => void,
|
|
||||||
) {
|
|
||||||
this._isResolved = false;
|
|
||||||
this._isRejected = false;
|
|
||||||
this._isCancelled = false;
|
|
||||||
this.cancelHandlers = [];
|
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
|
||||||
this._resolve = resolve;
|
|
||||||
this._reject = reject;
|
|
||||||
|
|
||||||
const onResolve = (value: T | PromiseLike<T>): 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<TResult1 = T, TResult2 = never>(
|
|
||||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
||||||
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
|
||||||
): Promise<TResult1 | TResult2> {
|
|
||||||
return this.promise.then(onFulfilled, onRejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public catch<TResult = never>(
|
|
||||||
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
|
||||||
): Promise<T | TResult> {
|
|
||||||
return this.promise.catch(onRejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import type { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
||||||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
|
||||||
|
|
||||||
type Headers = Record<string, string>;
|
|
||||||
type Middleware<T> = (value: T) => T | Promise<T>;
|
|
||||||
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
|
|
||||||
|
|
||||||
export class Interceptors<T> {
|
|
||||||
_fns: Middleware<T>[];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._fns = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
eject(fn: Middleware<T>): 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<T>): 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<Headers> | undefined;
|
|
||||||
PASSWORD?: string | Resolver<string> | undefined;
|
|
||||||
TOKEN?: string | Resolver<string> | undefined;
|
|
||||||
USERNAME?: string | Resolver<string> | undefined;
|
|
||||||
VERSION: string;
|
|
||||||
WITH_CREDENTIALS: boolean;
|
|
||||||
interceptors: {
|
|
||||||
request: Interceptors<AxiosRequestConfig>;
|
|
||||||
response: Interceptors<AxiosResponse>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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, unknown>): 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<T> = (options: ApiRequestOptions<T>) => Promise<T>;
|
|
||||||
|
|
||||||
export const resolve = async <T>(
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
resolver?: T | Resolver<T>,
|
|
||||||
): Promise<T | undefined> => {
|
|
||||||
if (typeof resolver === "function") {
|
|
||||||
return (resolver as Resolver<T>)(options);
|
|
||||||
}
|
|
||||||
return resolver;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHeaders = async <T>(
|
|
||||||
config: OpenAPIConfig,
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
): Promise<Record<string, string>> => {
|
|
||||||
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<string, string>,
|
|
||||||
);
|
|
||||||
|
|
||||||
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 <T>(
|
|
||||||
config: OpenAPIConfig,
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
url: string,
|
|
||||||
body: unknown,
|
|
||||||
formData: FormData | undefined,
|
|
||||||
headers: Record<string, string>,
|
|
||||||
onCancel: OnCancel,
|
|
||||||
axiosClient: AxiosInstance,
|
|
||||||
): Promise<AxiosResponse<T>> => {
|
|
||||||
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<T>;
|
|
||||||
if (axiosError.response) {
|
|
||||||
return axiosError.response;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getResponseHeader = (
|
|
||||||
response: AxiosResponse<unknown>,
|
|
||||||
responseHeader?: string,
|
|
||||||
): string | undefined => {
|
|
||||||
if (responseHeader) {
|
|
||||||
const content = response.headers[responseHeader];
|
|
||||||
if (isString(content)) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
|
|
||||||
if (response.status !== 204) {
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const catchErrorCodes = (
|
|
||||||
options: ApiRequestOptions,
|
|
||||||
result: ApiResult,
|
|
||||||
): void => {
|
|
||||||
const errors: Record<number, string> = {
|
|
||||||
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<T>
|
|
||||||
* @throws ApiError
|
|
||||||
*/
|
|
||||||
export const request = <T>(
|
|
||||||
config: OpenAPIConfig,
|
|
||||||
options: ApiRequestOptions<T>,
|
|
||||||
axiosClient: AxiosInstance = axios,
|
|
||||||
): CancelablePromise<T> => {
|
|
||||||
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<T>(
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,893 +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,
|
|
||||||
V1RoomsUpdateData,
|
|
||||||
V1RoomsUpdateResponse,
|
|
||||||
V1RoomsDeleteData,
|
|
||||||
V1RoomsDeleteResponse,
|
|
||||||
V1RoomsCreateMeetingData,
|
|
||||||
V1RoomsCreateMeetingResponse,
|
|
||||||
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<MetricsResponse> {
|
|
||||||
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<V1MeetingAudioConsentResponse> {
|
|
||||||
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_Room_ Successful Response
|
|
||||||
* @throws ApiError
|
|
||||||
*/
|
|
||||||
public v1RoomsList(
|
|
||||||
data: V1RoomsListData = {},
|
|
||||||
): CancelablePromise<V1RoomsListResponse> {
|
|
||||||
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<V1RoomsCreateResponse> {
|
|
||||||
return this.httpRequest.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "/v1/rooms",
|
|
||||||
body: data.requestBody,
|
|
||||||
mediaType: "application/json",
|
|
||||||
errors: {
|
|
||||||
422: "Validation Error",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rooms Update
|
|
||||||
* @param data The data for the request.
|
|
||||||
* @param data.roomId
|
|
||||||
* @param data.requestBody
|
|
||||||
* @returns Room Successful Response
|
|
||||||
* @throws ApiError
|
|
||||||
*/
|
|
||||||
public v1RoomsUpdate(
|
|
||||||
data: V1RoomsUpdateData,
|
|
||||||
): CancelablePromise<V1RoomsUpdateResponse> {
|
|
||||||
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<V1RoomsDeleteResponse> {
|
|
||||||
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<V1RoomsCreateMeetingResponse> {
|
|
||||||
return this.httpRequest.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "/v1/rooms/{room_name}/meeting",
|
|
||||||
path: {
|
|
||||||
room_name: data.roomName,
|
|
||||||
},
|
|
||||||
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<V1TranscriptsListResponse> {
|
|
||||||
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<V1TranscriptsCreateResponse> {
|
|
||||||
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<V1TranscriptsSearchResponse> {
|
|
||||||
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<V1TranscriptGetResponse> {
|
|
||||||
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<V1TranscriptUpdateResponse> {
|
|
||||||
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<V1TranscriptDeleteResponse> {
|
|
||||||
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<V1TranscriptGetTopicsResponse> {
|
|
||||||
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<V1TranscriptGetTopicsWithWordsResponse> {
|
|
||||||
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<V1TranscriptGetTopicsWithWordsPerSpeakerResponse> {
|
|
||||||
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<V1TranscriptPostToZulipResponse> {
|
|
||||||
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<V1TranscriptHeadAudioMp3Response> {
|
|
||||||
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<V1TranscriptGetAudioMp3Response> {
|
|
||||||
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<V1TranscriptGetAudioWaveformResponse> {
|
|
||||||
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<V1TranscriptGetParticipantsResponse> {
|
|
||||||
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<V1TranscriptAddParticipantResponse> {
|
|
||||||
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<V1TranscriptGetParticipantResponse> {
|
|
||||||
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<V1TranscriptUpdateParticipantResponse> {
|
|
||||||
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<V1TranscriptDeleteParticipantResponse> {
|
|
||||||
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<V1TranscriptAssignSpeakerResponse> {
|
|
||||||
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<V1TranscriptMergeSpeakerResponse> {
|
|
||||||
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<V1TranscriptRecordUploadResponse> {
|
|
||||||
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<V1TranscriptGetWebsocketEventsResponse> {
|
|
||||||
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<V1TranscriptRecordWebrtcResponse> {
|
|
||||||
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<V1TranscriptProcessResponse> {
|
|
||||||
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<V1UserMeResponse> {
|
|
||||||
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<V1ZulipGetStreamsResponse> {
|
|
||||||
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<V1ZulipGetTopicsResponse> {
|
|
||||||
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<V1WherebyWebhookResponse> {
|
|
||||||
return this.httpRequest.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "/v1/whereby",
|
|
||||||
body: data.requestBody,
|
|
||||||
mediaType: "application/json",
|
|
||||||
errors: {
|
|
||||||
422: "Validation Error",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
// TODO better connection with generated schema; it's duplication
|
|
||||||
export const RECORD_A_MEETING_URL = "/transcripts/new" as const;
|
|
||||||
33
www/app/lib/ApiAuthProvider.tsx
Normal file
33
www/app/lib/ApiAuthProvider.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useContext } from "react";
|
||||||
|
import { client, configureApiAuth } from "./apiClient";
|
||||||
|
import useSessionAccessToken from "./useSessionAccessToken";
|
||||||
|
import { DomainContext } from "../domainContext";
|
||||||
|
|
||||||
|
export function ApiAuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const { accessToken } = useSessionAccessToken();
|
||||||
|
const { api_url } = useContext(DomainContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Configure base URL
|
||||||
|
if (api_url) {
|
||||||
|
client.use({
|
||||||
|
onRequest({ request }) {
|
||||||
|
// Update the base URL for all requests
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const apiUrl = new URL(api_url);
|
||||||
|
url.protocol = apiUrl.protocol;
|
||||||
|
url.host = apiUrl.host;
|
||||||
|
url.port = apiUrl.port;
|
||||||
|
return new Request(url.toString(), request);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure authentication
|
||||||
|
configureApiAuth(accessToken);
|
||||||
|
}, [accessToken, api_url]);
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
110
www/app/lib/api-hooks.ts
Normal file
110
www/app/lib/api-hooks.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { $api } from "./apiClient";
|
||||||
|
import { useError } from "../(errors)/errorContext";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import type { paths } from "../reflector-api";
|
||||||
|
|
||||||
|
// Rooms hooks
|
||||||
|
export function useRoomsList(page: number = 1) {
|
||||||
|
const { setError } = useError();
|
||||||
|
|
||||||
|
return $api.useQuery(
|
||||||
|
"get",
|
||||||
|
"/v1/rooms",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
query: { page },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error fetching the rooms");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcripts hooks
|
||||||
|
export function useTranscriptsSearch(
|
||||||
|
q: string = "",
|
||||||
|
options: {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
room_id?: string;
|
||||||
|
source_kind?: string;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const { setError } = useError();
|
||||||
|
|
||||||
|
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 as any,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error searching transcripts");
|
||||||
|
},
|
||||||
|
keepPreviousData: true, // For smooth pagination
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranscriptDelete() {
|
||||||
|
const { setError } = useError();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return $api.useMutation("delete", "/v1/transcripts/{transcript_id}", {
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate transcripts queries to refetch
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions("get", "/v1/transcripts/search").queryKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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 { setError } = useError();
|
||||||
|
|
||||||
|
return $api.useQuery(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
transcript_id: transcriptId || "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!transcriptId,
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error loading the transcript");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
40
www/app/lib/apiClient.tsx
Normal file
40
www/app/lib/apiClient.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"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";
|
||||||
|
|
||||||
|
// Create the base openapi-fetch client
|
||||||
|
export const client = createClient<paths>({
|
||||||
|
// Base URL will be set dynamically via middleware
|
||||||
|
baseUrl: "",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the React Query client wrapper
|
||||||
|
export const $api = createFetchClient<paths>(client);
|
||||||
|
|
||||||
|
// Configure authentication
|
||||||
|
export const configureApiAuth = (token: string | null | undefined) => {
|
||||||
|
if (token) {
|
||||||
|
client.use({
|
||||||
|
onRequest({ request }) {
|
||||||
|
request.headers.set("Authorization", `Bearer ${token}`);
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export typed hooks for convenience
|
||||||
|
export const useApiQuery = $api.useQuery;
|
||||||
|
export const useApiMutation = $api.useMutation;
|
||||||
|
export const useApiSuspenseQuery = $api.useSuspenseQuery;
|
||||||
17
www/app/lib/queryClient.tsx
Normal file
17
www/app/lib/queryClient.tsx
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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<OpenApi | null>(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;
|
|
||||||
}
|
|
||||||
@@ -6,16 +6,23 @@ import system from "./styles/theme";
|
|||||||
import { WherebyProvider } from "@whereby.com/browser-sdk/react";
|
import { WherebyProvider } from "@whereby.com/browser-sdk/react";
|
||||||
import { Toaster } from "./components/ui/toaster";
|
import { Toaster } from "./components/ui/toaster";
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { queryClient } from "./lib/queryClient";
|
||||||
|
import { ApiAuthProvider } from "./lib/ApiAuthProvider";
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<NuqsAdapter>
|
<NuqsAdapter>
|
||||||
<ChakraProvider value={system}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<WherebyProvider>
|
<ApiAuthProvider>
|
||||||
{children}
|
<ChakraProvider value={system}>
|
||||||
<Toaster />
|
<WherebyProvider>
|
||||||
</WherebyProvider>
|
{children}
|
||||||
</ChakraProvider>
|
<Toaster />
|
||||||
|
</WherebyProvider>
|
||||||
|
</ChakraProvider>
|
||||||
|
</ApiAuthProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
</NuqsAdapter>
|
</NuqsAdapter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
2170
www/app/reflector-api.d.ts
vendored
Normal file
2170
www/app/reflector-api.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"openapi": "openapi-ts"
|
"codegen": "openapi-typescript http://127.0.0.1:1250/openapi.json -o ./app/reflector-api.d.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^3.24.2",
|
"@chakra-ui/react": "^3.24.2",
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@sentry/nextjs": "^7.77.0",
|
"@sentry/nextjs": "^7.77.0",
|
||||||
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"@vercel/edge-config": "^0.4.1",
|
"@vercel/edge-config": "^0.4.1",
|
||||||
"@vercel/kv": "^2.0.0",
|
"@vercel/kv": "^2.0.0",
|
||||||
"@whereby.com/browser-sdk": "^3.3.4",
|
"@whereby.com/browser-sdk": "^3.3.4",
|
||||||
@@ -32,6 +33,8 @@
|
|||||||
"next-auth": "^4.24.7",
|
"next-auth": "^4.24.7",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"nuqs": "^2.4.3",
|
"nuqs": "^2.4.3",
|
||||||
|
"openapi-fetch": "^0.14.0",
|
||||||
|
"openapi-react-query": "^0.5.0",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -53,8 +56,8 @@
|
|||||||
"author": "Andreas <andreas@monadical.com>",
|
"author": "Andreas <andreas@monadical.com>",
|
||||||
"license": "All Rights Reserved",
|
"license": "All Rights Reserved",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "^0.48.0",
|
|
||||||
"@types/react": "18.2.20",
|
"@types/react": "18.2.20",
|
||||||
|
"openapi-typescript": "^7.9.1",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"vercel": "^37.3.0"
|
"vercel": "^37.3.0"
|
||||||
},
|
},
|
||||||
|
|||||||
574
www/pnpm-lock.yaml
generated
574
www/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user