mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: Add Zod-based typing for API responses across frontend and backend (#1892)
## Summary Define API response Zod schemas in common-utils for alerts, webhooks, team, installation, and me endpoints. Apply Response<T> typing on backend route handlers with explicit Mongoose-to-JSON serialization (ObjectId, Date, Map). Replace all `any` types and `as Promise<T>` casts in frontend TanStack Query hooks with proper generics. ### How to test locally or on Vercel 1. `yarn dev` 2. Interact with app, ensuring nothing is broken ### References - Linear Issue: HDX-3464
This commit is contained in:
parent
9682eb4d51
commit
359b58744e
12 changed files with 498 additions and 240 deletions
7
.changeset/slow-insects-travel.md
Normal file
7
.changeset/slow-insects-travel.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'@hyperdx/common-utils': patch
|
||||
'@hyperdx/api': patch
|
||||
'@hyperdx/app': patch
|
||||
---
|
||||
|
||||
fix: add explicit api typing to all api routes and frontend hooks
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { AlertsApiResponse } from '@hyperdx/common-utils/dist/types';
|
||||
import express from 'express';
|
||||
import _ from 'lodash';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { z } from 'zod';
|
||||
import { processRequest, validateRequest } from 'zod-express-middleware';
|
||||
|
|
@ -17,7 +17,8 @@ import { alertSchema, objectIdSchema } from '@/utils/zod';
|
|||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
type AlertsExpRes = express.Response<AlertsApiResponse>;
|
||||
router.get('/', async (req, res: AlertsExpRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -26,7 +27,7 @@ router.get('/', async (req, res, next) => {
|
|||
|
||||
const alerts = await getAlertsEnhanced(teamId);
|
||||
|
||||
const data = await Promise.all(
|
||||
const data: AlertsApiResponse['data'] = await Promise.all(
|
||||
alerts.map(async alert => {
|
||||
const history = await getRecentAlertHistories({
|
||||
alertId: new ObjectId(alert._id),
|
||||
|
|
@ -34,50 +35,79 @@ router.get('/', async (req, res, next) => {
|
|||
});
|
||||
|
||||
return {
|
||||
history,
|
||||
_id: alert._id.toString(),
|
||||
interval: alert.interval,
|
||||
scheduleOffsetMinutes: alert.scheduleOffsetMinutes,
|
||||
scheduleStartAt: alert.scheduleStartAt?.toISOString() ?? undefined,
|
||||
threshold: alert.threshold,
|
||||
thresholdType: alert.thresholdType,
|
||||
channel: { type: alert.channel.type ?? undefined },
|
||||
state: alert.state,
|
||||
source: alert.source,
|
||||
tileId: alert.tileId,
|
||||
name: alert.name,
|
||||
message: alert.message,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
createdAt: (alert as any).createdAt?.toISOString?.() ?? '',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
updatedAt: (alert as any).updatedAt?.toISOString?.() ?? '',
|
||||
history: history.map(h => ({
|
||||
counts: h.counts,
|
||||
createdAt: h.createdAt.toISOString(),
|
||||
state: h.state,
|
||||
lastValues: h.lastValues.map(lv => ({
|
||||
startTime: lv.startTime.toISOString(),
|
||||
count: lv.count,
|
||||
})),
|
||||
})),
|
||||
silenced: alert.silenced
|
||||
? {
|
||||
by: alert.silenced.by?.email,
|
||||
at: alert.silenced.at,
|
||||
until: alert.silenced.until,
|
||||
by: alert.silenced.by?.email ?? '',
|
||||
at: alert.silenced.at.toISOString(),
|
||||
until: alert.silenced.until.toISOString(),
|
||||
}
|
||||
: undefined,
|
||||
createdBy: alert.createdBy
|
||||
? _.pick(alert.createdBy, ['email', 'name'])
|
||||
? {
|
||||
email: alert.createdBy.email,
|
||||
name: alert.createdBy.name,
|
||||
}
|
||||
: undefined,
|
||||
dashboardId: alert.dashboard
|
||||
? alert.dashboard._id.toString()
|
||||
: undefined,
|
||||
savedSearchId: alert.savedSearch
|
||||
? alert.savedSearch._id.toString()
|
||||
: undefined,
|
||||
dashboard: alert.dashboard
|
||||
? {
|
||||
_id: alert.dashboard._id.toString(),
|
||||
name: alert.dashboard.name,
|
||||
updatedAt:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(alert as any).dashboard?.updatedAt?.toISOString?.() ?? '',
|
||||
tags: alert.dashboard.tags,
|
||||
tiles: alert.dashboard.tiles
|
||||
.filter(tile => tile.id === alert.tileId)
|
||||
.map(tile => ({
|
||||
id: tile.id,
|
||||
config: { name: tile.config.name },
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
savedSearch: alert.savedSearch
|
||||
? {
|
||||
_id: alert.savedSearch._id.toString(),
|
||||
createdAt:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(alert as any).savedSearch?.createdAt?.toISOString?.() ?? '',
|
||||
name: alert.savedSearch.name,
|
||||
updatedAt:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(alert as any).savedSearch?.updatedAt?.toISOString?.() ?? '',
|
||||
tags: alert.savedSearch.tags,
|
||||
}
|
||||
: undefined,
|
||||
channel: _.pick(alert.channel, ['type']),
|
||||
...(alert.dashboard && {
|
||||
dashboardId: alert.dashboard._id,
|
||||
dashboard: {
|
||||
tiles: alert.dashboard.tiles
|
||||
.filter(tile => tile.id === alert.tileId)
|
||||
.map(tile => _.pick(tile, ['id', 'config.name'])),
|
||||
..._.pick(alert.dashboard, ['_id', 'name', 'updatedAt', 'tags']),
|
||||
},
|
||||
}),
|
||||
...(alert.savedSearch && {
|
||||
savedSearchId: alert.savedSearch._id,
|
||||
savedSearch: _.pick(alert.savedSearch, [
|
||||
'_id',
|
||||
'createdAt',
|
||||
'name',
|
||||
'updatedAt',
|
||||
'tags',
|
||||
]),
|
||||
}),
|
||||
..._.pick(alert, [
|
||||
'_id',
|
||||
'interval',
|
||||
'scheduleOffsetMinutes',
|
||||
'scheduleStartAt',
|
||||
'threshold',
|
||||
'thresholdType',
|
||||
'state',
|
||||
'source',
|
||||
'tileId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
]),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import {
|
||||
type MeApiResponse,
|
||||
MeApiResponseSchema,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import express from 'express';
|
||||
|
||||
import { AI_API_KEY, ANTHROPIC_API_KEY, USAGE_STATS_ENABLED } from '@/config';
|
||||
|
|
@ -6,7 +10,7 @@ import { Api404Error } from '@/utils/errors';
|
|||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
router.get('/', async (req, res: express.Response<MeApiResponse>, next) => {
|
||||
try {
|
||||
if (req.user == null) {
|
||||
throw new Api404Error('Request without user found');
|
||||
|
|
@ -22,14 +26,17 @@ router.get('/', async (req, res, next) => {
|
|||
} = req.user;
|
||||
|
||||
const team = await getTeam(teamId);
|
||||
if (team == null) {
|
||||
throw new Api404Error(`Team not found for user ${id}`);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
accessKey,
|
||||
createdAt,
|
||||
createdAt: createdAt.toISOString(),
|
||||
email,
|
||||
id,
|
||||
id: id.toString(),
|
||||
name,
|
||||
team,
|
||||
team: MeApiResponseSchema.shape.team.parse(team.toJSON()),
|
||||
usageStatsEnabled: USAGE_STATS_ENABLED,
|
||||
aiAssistantEnabled: !!(AI_API_KEY || ANTHROPIC_API_KEY),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { InstallationApiResponse } from '@hyperdx/common-utils/dist/types';
|
||||
import express from 'express';
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { z } from 'zod';
|
||||
|
|
@ -53,7 +54,8 @@ router.get('/health', async (req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
router.get('/installation', async (req, res, next) => {
|
||||
type InstallationEspRes = express.Response<InstallationApiResponse>;
|
||||
router.get('/installation', async (_, res: InstallationEspRes, next) => {
|
||||
try {
|
||||
const _isTeamExisting = await isTeamExisting();
|
||||
return res.json({
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
import type {
|
||||
RotateApiKeyApiResponse,
|
||||
TeamApiResponse,
|
||||
TeamInvitationsApiResponse,
|
||||
TeamMembersApiResponse,
|
||||
TeamTagsApiResponse,
|
||||
UpdateClickHouseSettingsApiResponse,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import { TeamClickHouseSettingsSchema } from '@hyperdx/common-utils/dist/types';
|
||||
import crypto from 'crypto';
|
||||
import express from 'express';
|
||||
|
|
@ -23,7 +31,8 @@ import { objectIdSchema } from '@/utils/zod';
|
|||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
type TeamApiExpRes = express.Response<TeamApiResponse>;
|
||||
router.get('/', async (req, res: TeamApiExpRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
const userId = req.user?._id;
|
||||
|
|
@ -46,20 +55,37 @@ router.get('/', async (req, res, next) => {
|
|||
throw new Error(`Team ${teamId} not found for user ${userId}`);
|
||||
}
|
||||
|
||||
res.json(team.toJSON());
|
||||
// createdAt comes from Mongoose timestamps but is not on the ITeam interface
|
||||
const createdAt =
|
||||
'createdAt' in team && team.createdAt instanceof Date
|
||||
? team.createdAt.toISOString()
|
||||
: '';
|
||||
|
||||
res.json({
|
||||
_id: team._id.toString(),
|
||||
allowedAuthMethods:
|
||||
'allowedAuthMethods' in team ? team.allowedAuthMethods : undefined,
|
||||
apiKey: team.apiKey,
|
||||
name: team.name,
|
||||
createdAt,
|
||||
});
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/apiKey', async (req, res, next) => {
|
||||
type RotateApiKeyExpRes = express.Response<RotateApiKeyApiResponse>;
|
||||
router.patch('/apiKey', async (req, res: RotateApiKeyExpRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
throw new Error(`User ${req.user?._id} not associated with a team`);
|
||||
}
|
||||
const team = await rotateTeamApiKey(teamId);
|
||||
res.json({ newApiKey: team?.apiKey });
|
||||
if (team?.apiKey == null) {
|
||||
throw new Error(`Failed to rotate API key for team ${teamId}`);
|
||||
}
|
||||
res.json({ newApiKey: team.apiKey });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
|
|
@ -92,7 +118,11 @@ router.patch(
|
|||
processRequest({
|
||||
body: TeamClickHouseSettingsSchema,
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
async (
|
||||
req,
|
||||
res: express.Response<UpdateClickHouseSettingsApiResponse>,
|
||||
next,
|
||||
) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -105,14 +135,7 @@ router.patch(
|
|||
|
||||
const team = await updateTeamClickhouseSettings(teamId, req.body);
|
||||
|
||||
res.json(
|
||||
Object.entries(req.body).reduce((acc, cur) => {
|
||||
return {
|
||||
...acc,
|
||||
[cur[0]]: team?.[cur[0]],
|
||||
};
|
||||
}, {} as any),
|
||||
);
|
||||
res.json(pick(team, Object.keys(req.body)));
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
|
|
@ -176,7 +199,8 @@ router.post(
|
|||
},
|
||||
);
|
||||
|
||||
router.get('/invitations', async (req, res, next) => {
|
||||
type TeamInviteExpressRes = express.Response<TeamInvitationsApiResponse>;
|
||||
router.get('/invitations', async (req, res: TeamInviteExpressRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -193,8 +217,8 @@ router.get('/invitations', async (req, res, next) => {
|
|||
);
|
||||
res.json({
|
||||
data: teamInvites.map(ti => ({
|
||||
_id: ti._id,
|
||||
createdAt: ti.createdAt,
|
||||
_id: ti._id.toString(),
|
||||
createdAt: ti.createdAt.toISOString(),
|
||||
email: ti.email,
|
||||
name: ti.name,
|
||||
url: `${config.FRONTEND_URL}/join-team?token=${ti.token}`,
|
||||
|
|
@ -225,7 +249,8 @@ router.delete(
|
|||
},
|
||||
);
|
||||
|
||||
router.get('/members', async (req, res, next) => {
|
||||
type TeamMembersExpRes = express.Response<TeamMembersApiResponse>;
|
||||
router.get('/members', async (req, res: TeamMembersExpRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
const userId = req.user?._id;
|
||||
|
|
@ -281,7 +306,8 @@ router.delete(
|
|||
},
|
||||
);
|
||||
|
||||
router.get('/tags', async (req, res, next) => {
|
||||
type TeamTagsExpRes = express.Response<TeamTagsApiResponse>;
|
||||
router.get('/tags', async (req, res: TeamTagsExpRes, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
import type {
|
||||
WebhookCreateApiResponse,
|
||||
WebhooksApiResponse,
|
||||
WebhookTestApiResponse,
|
||||
WebhookUpdateApiResponse,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import express from 'express';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import mongoose from 'mongoose';
|
||||
|
|
@ -23,7 +29,7 @@ router.get(
|
|||
]),
|
||||
}),
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
async (req, res: express.Response<WebhooksApiResponse>, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -35,7 +41,7 @@ router.get(
|
|||
{ __v: 0, team: 0 },
|
||||
);
|
||||
res.json({
|
||||
data: webhooks,
|
||||
data: webhooks.map(w => w.toJSON({ flattenMaps: true })),
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
|
|
@ -75,7 +81,11 @@ router.post(
|
|||
url: z.string().url(),
|
||||
}),
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
async (
|
||||
req,
|
||||
res: express.Response<WebhookCreateApiResponse | { message: string }>,
|
||||
next,
|
||||
) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -100,7 +110,7 @@ router.post(
|
|||
});
|
||||
await webhook.save();
|
||||
res.json({
|
||||
data: webhook,
|
||||
data: webhook.toJSON({ flattenMaps: true }),
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
|
|
@ -128,7 +138,11 @@ router.put(
|
|||
url: z.string().url(),
|
||||
}),
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
async (
|
||||
req,
|
||||
res: express.Response<WebhookUpdateApiResponse | { message: string }>,
|
||||
next,
|
||||
) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
@ -177,8 +191,14 @@ router.put(
|
|||
{ new: true, select: { __v: 0, team: 0 } },
|
||||
);
|
||||
|
||||
if (!updatedWebhook) {
|
||||
return res.status(404).json({
|
||||
message: 'Webhook not found after update',
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
data: updatedWebhook,
|
||||
data: updatedWebhook.toJSON({ flattenMaps: true }),
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
|
|
@ -222,7 +242,7 @@ router.post(
|
|||
url: z.string().url(),
|
||||
}),
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
async (req, res: express.Response<WebhookTestApiResponse>, next) => {
|
||||
try {
|
||||
const teamId = req.user?.team;
|
||||
if (teamId == null) {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,21 @@ import type { HTTPError, Options, ResponsePromise } from 'ky';
|
|||
import ky from 'ky-universal';
|
||||
import type {
|
||||
Alert,
|
||||
AlertsApiResponse,
|
||||
InstallationApiResponse,
|
||||
MeApiResponse,
|
||||
PresetDashboard,
|
||||
PresetDashboardFilter,
|
||||
Team,
|
||||
RotateApiKeyApiResponse,
|
||||
TeamApiResponse,
|
||||
TeamInvitationsApiResponse,
|
||||
TeamMembersApiResponse,
|
||||
TeamTagsApiResponse,
|
||||
UpdateClickHouseSettingsApiResponse,
|
||||
WebhookCreateApiResponse,
|
||||
WebhooksApiResponse,
|
||||
WebhookTestApiResponse,
|
||||
WebhookUpdateApiResponse,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
|
@ -16,8 +28,6 @@ import {
|
|||
fetchLocalDashboards,
|
||||
getLocalDashboardTags,
|
||||
} from './dashboard';
|
||||
import type { AlertsPageItem } from './types';
|
||||
|
||||
type ServicesResponse = {
|
||||
data: Record<
|
||||
string,
|
||||
|
|
@ -30,12 +40,6 @@ type ServicesResponse = {
|
|||
>;
|
||||
};
|
||||
|
||||
type AlertsResponse = {
|
||||
data: AlertsPageItem[];
|
||||
};
|
||||
|
||||
type ApiAlertInput = Alert;
|
||||
|
||||
export function loginHook(request: Request, options: any, response: Response) {
|
||||
// marketing pages
|
||||
const WHITELIST_PATHS = [
|
||||
|
|
@ -76,7 +80,7 @@ export const hdxServer = (
|
|||
|
||||
const api = {
|
||||
useCreateAlert() {
|
||||
return useMutation<any, Error, ApiAlertInput>({
|
||||
return useMutation<{ data: Alert }, Error, Alert>({
|
||||
mutationFn: async alert =>
|
||||
server('alerts', {
|
||||
method: 'POST',
|
||||
|
|
@ -85,7 +89,7 @@ const api = {
|
|||
});
|
||||
},
|
||||
useUpdateAlert() {
|
||||
return useMutation<any, Error, { id: string } & ApiAlertInput>({
|
||||
return useMutation<{ data: Alert }, Error, { id: string } & Alert>({
|
||||
mutationFn: async alert =>
|
||||
server(`alerts/${alert.id}`, {
|
||||
method: 'PUT',
|
||||
|
|
@ -94,28 +98,31 @@ const api = {
|
|||
});
|
||||
},
|
||||
useDeleteAlert() {
|
||||
return useMutation<any, Error, string>({
|
||||
mutationFn: async (alertId: string) =>
|
||||
server(`alerts/${alertId}`, {
|
||||
return useMutation<void, Error, string>({
|
||||
mutationFn: async (alertId: string) => {
|
||||
await server(`alerts/${alertId}`, {
|
||||
method: 'DELETE',
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
useSilenceAlert() {
|
||||
return useMutation<any, Error, { alertId: string; mutedUntil: string }>({
|
||||
mutationFn: async ({ alertId, mutedUntil }) =>
|
||||
server(`alerts/${alertId}/silenced`, {
|
||||
return useMutation<void, Error, { alertId: string; mutedUntil: string }>({
|
||||
mutationFn: async ({ alertId, mutedUntil }) => {
|
||||
await server(`alerts/${alertId}/silenced`, {
|
||||
method: 'POST',
|
||||
json: { mutedUntil },
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
useUnsilenceAlert() {
|
||||
return useMutation<any, Error, string>({
|
||||
mutationFn: async (alertId: string) =>
|
||||
server(`alerts/${alertId}/silenced`, {
|
||||
return useMutation<void, Error, string>({
|
||||
mutationFn: async (alertId: string) => {
|
||||
await server(`alerts/${alertId}/silenced`, {
|
||||
method: 'DELETE',
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
useDashboards(options?: UseQueryOptions<Dashboard[] | null, Error>) {
|
||||
|
|
@ -136,14 +143,14 @@ const api = {
|
|||
tags,
|
||||
}: {
|
||||
name: string;
|
||||
charts: any;
|
||||
query: any;
|
||||
tags: any;
|
||||
charts: Dashboard['tiles'];
|
||||
query: string;
|
||||
tags: string[];
|
||||
}) =>
|
||||
hdxServer(`dashboards`, {
|
||||
method: 'POST',
|
||||
json: { name, charts, query, tags },
|
||||
}).json(),
|
||||
}).json<Dashboard>(),
|
||||
});
|
||||
},
|
||||
useUpdateDashboard() {
|
||||
|
|
@ -157,22 +164,23 @@ const api = {
|
|||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
charts: any;
|
||||
query: any;
|
||||
tags: any;
|
||||
charts: Dashboard['tiles'];
|
||||
query: string;
|
||||
tags: string[];
|
||||
}) =>
|
||||
hdxServer(`dashboards/${id}`, {
|
||||
method: 'PUT',
|
||||
json: { name, charts, query, tags },
|
||||
}).json(),
|
||||
}).json<Dashboard>(),
|
||||
});
|
||||
},
|
||||
useDeleteDashboard() {
|
||||
return useMutation({
|
||||
mutationFn: async ({ id }: { id: string }) =>
|
||||
hdxServer(`dashboards/${id}`, {
|
||||
mutationFn: async ({ id }: { id: string }) => {
|
||||
await hdxServer(`dashboards/${id}`, {
|
||||
method: 'DELETE',
|
||||
}).json(),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
usePresetDashboardFilters(
|
||||
|
|
@ -186,30 +194,34 @@ const api = {
|
|||
hdxServer(`dashboards/preset/${presetDashboard}/filters/`, {
|
||||
method: 'GET',
|
||||
searchParams: { sourceId },
|
||||
}).json() as Promise<PresetDashboardFilter[]>,
|
||||
}).json<PresetDashboardFilter[]>(),
|
||||
enabled: !!sourceId && enabled,
|
||||
});
|
||||
},
|
||||
useCreatePresetDashboardFilter() {
|
||||
return useMutation({
|
||||
return useMutation<PresetDashboardFilter, Error, PresetDashboardFilter>({
|
||||
mutationFn: async (filter: PresetDashboardFilter) =>
|
||||
hdxServer(`dashboards/preset/${filter.presetDashboard}/filter`, {
|
||||
method: 'POST',
|
||||
json: { filter },
|
||||
}).json(),
|
||||
}).json<PresetDashboardFilter>(),
|
||||
});
|
||||
},
|
||||
useUpdatePresetDashboardFilter() {
|
||||
return useMutation({
|
||||
return useMutation<PresetDashboardFilter, Error, PresetDashboardFilter>({
|
||||
mutationFn: async (filter: PresetDashboardFilter) =>
|
||||
hdxServer(`dashboards/preset/${filter.presetDashboard}/filter`, {
|
||||
method: 'PUT',
|
||||
json: { filter },
|
||||
}).json(),
|
||||
}).json<PresetDashboardFilter>(),
|
||||
});
|
||||
},
|
||||
useDeletePresetDashboardFilter() {
|
||||
return useMutation({
|
||||
return useMutation<
|
||||
PresetDashboardFilter,
|
||||
Error,
|
||||
{ id: string; presetDashboard: PresetDashboard }
|
||||
>({
|
||||
mutationFn: async ({
|
||||
id,
|
||||
presetDashboard,
|
||||
|
|
@ -219,13 +231,13 @@ const api = {
|
|||
}) =>
|
||||
hdxServer(`dashboards/preset/${presetDashboard}/filter/${id}`, {
|
||||
method: 'DELETE',
|
||||
}).json(),
|
||||
}).json<PresetDashboardFilter>(),
|
||||
});
|
||||
},
|
||||
useAlerts() {
|
||||
return useQuery({
|
||||
queryKey: [`alerts`],
|
||||
queryFn: () => hdxServer(`alerts`).json() as Promise<AlertsResponse>,
|
||||
queryFn: () => hdxServer(`alerts`).json<AlertsApiResponse>(),
|
||||
});
|
||||
},
|
||||
useServices() {
|
||||
|
|
@ -234,34 +246,39 @@ const api = {
|
|||
queryFn: () =>
|
||||
hdxServer(`chart/services`, {
|
||||
method: 'GET',
|
||||
}).json() as Promise<ServicesResponse>,
|
||||
}).json<ServicesResponse>(),
|
||||
});
|
||||
},
|
||||
useRotateTeamApiKey() {
|
||||
return useMutation<any, Error | HTTPError>({
|
||||
return useMutation<RotateApiKeyApiResponse, Error | HTTPError>({
|
||||
mutationFn: async () =>
|
||||
hdxServer(`team/apiKey`, {
|
||||
method: 'PATCH',
|
||||
}).json(),
|
||||
}).json<RotateApiKeyApiResponse>(),
|
||||
});
|
||||
},
|
||||
useDeleteTeamMember() {
|
||||
return useMutation<any, Error | HTTPError, { userId: string }>({
|
||||
return useMutation<
|
||||
{ message: string },
|
||||
Error | HTTPError,
|
||||
{ userId: string }
|
||||
>({
|
||||
mutationFn: async ({ userId }: { userId: string }) =>
|
||||
hdxServer(`team/member/${userId}`, {
|
||||
method: 'DELETE',
|
||||
}).json(),
|
||||
}).json<{ message: string }>(),
|
||||
});
|
||||
},
|
||||
useTeamInvitations() {
|
||||
return useQuery<any>({
|
||||
return useQuery<TeamInvitationsApiResponse>({
|
||||
queryKey: [`team/invitations`],
|
||||
queryFn: () => hdxServer(`team/invitations`).json(),
|
||||
queryFn: () =>
|
||||
hdxServer(`team/invitations`).json<TeamInvitationsApiResponse>(),
|
||||
});
|
||||
},
|
||||
useSaveTeamInvitation() {
|
||||
return useMutation<
|
||||
any,
|
||||
{ url: string },
|
||||
Error | HTTPError,
|
||||
{ name?: string; email: string }
|
||||
>({
|
||||
|
|
@ -272,7 +289,7 @@ const api = {
|
|||
name,
|
||||
email,
|
||||
},
|
||||
}).json(),
|
||||
}).json<{ url: string }>(),
|
||||
});
|
||||
},
|
||||
useDeleteTeamInvitation() {
|
||||
|
|
@ -280,28 +297,28 @@ const api = {
|
|||
mutationFn: async ({ id }: { id: string }) =>
|
||||
hdxServer(`team/invitation/${id}`, {
|
||||
method: 'DELETE',
|
||||
}).json(),
|
||||
}).json<{ message: string }>(),
|
||||
});
|
||||
},
|
||||
useInstallation() {
|
||||
return useQuery<any, Error>({
|
||||
return useQuery<InstallationApiResponse | undefined, Error>({
|
||||
queryKey: [`installation`],
|
||||
queryFn: () => {
|
||||
if (IS_LOCAL_MODE) {
|
||||
return;
|
||||
}
|
||||
return hdxServer(`installation`).json();
|
||||
return hdxServer(`installation`).json<InstallationApiResponse>();
|
||||
},
|
||||
});
|
||||
},
|
||||
useMe() {
|
||||
return useQuery<any>({
|
||||
return useQuery<MeApiResponse | null>({
|
||||
queryKey: [`me`],
|
||||
queryFn: () => {
|
||||
if (IS_LOCAL_MODE) {
|
||||
return null;
|
||||
}
|
||||
return hdxServer(`me`).json();
|
||||
return hdxServer(`me`).json<MeApiResponse>();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
@ -312,37 +329,29 @@ const api = {
|
|||
if (IS_LOCAL_MODE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
type TeamResponse = Pick<
|
||||
Team,
|
||||
'allowedAuthMethods' | 'apiKey' | 'name'
|
||||
> & {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
return hdxServer(`team`).json<TeamResponse>();
|
||||
return hdxServer(`team`).json<TeamApiResponse>();
|
||||
},
|
||||
retry: 1,
|
||||
});
|
||||
},
|
||||
useTeamMembers() {
|
||||
return useQuery<any>({
|
||||
return useQuery<TeamMembersApiResponse>({
|
||||
queryKey: [`team/members`],
|
||||
queryFn: () => hdxServer(`team/members`).json(),
|
||||
queryFn: () => hdxServer(`team/members`).json<TeamMembersApiResponse>(),
|
||||
});
|
||||
},
|
||||
useSetTeamName() {
|
||||
return useMutation<any, HTTPError, { name: string }>({
|
||||
return useMutation<{ name: string }, HTTPError, { name: string }>({
|
||||
mutationFn: async ({ name }) =>
|
||||
hdxServer(`team/name`, {
|
||||
method: 'PATCH',
|
||||
json: { name },
|
||||
}).json(),
|
||||
}).json<{ name: string }>(),
|
||||
});
|
||||
},
|
||||
useUpdateClickhouseSettings() {
|
||||
return useMutation<
|
||||
any,
|
||||
UpdateClickHouseSettingsApiResponse,
|
||||
HTTPError,
|
||||
{
|
||||
searchRowLimit?: number;
|
||||
|
|
@ -354,7 +363,7 @@ const api = {
|
|||
hdxServer(`team/clickhouse-settings`, {
|
||||
method: 'PATCH',
|
||||
json: settings,
|
||||
}).json(),
|
||||
}).json<UpdateClickHouseSettingsApiResponse>(),
|
||||
});
|
||||
},
|
||||
useTags() {
|
||||
|
|
@ -362,12 +371,12 @@ const api = {
|
|||
queryKey: [`team/tags`],
|
||||
queryFn: IS_LOCAL_MODE
|
||||
? async () => ({ data: getLocalDashboardTags() })
|
||||
: () => hdxServer(`team/tags`).json<{ data: string[] }>(),
|
||||
: () => hdxServer(`team/tags`).json<TeamTagsApiResponse>(),
|
||||
});
|
||||
},
|
||||
useSaveWebhook() {
|
||||
return useMutation<
|
||||
any,
|
||||
WebhookCreateApiResponse,
|
||||
Error | HTTPError,
|
||||
{
|
||||
service: string;
|
||||
|
|
@ -387,14 +396,6 @@ const api = {
|
|||
queryParams,
|
||||
headers,
|
||||
body,
|
||||
}: {
|
||||
service: string;
|
||||
url: string;
|
||||
name: string;
|
||||
description: string;
|
||||
queryParams?: Record<string, string>;
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
}) =>
|
||||
hdxServer(`webhooks`, {
|
||||
method: 'POST',
|
||||
|
|
@ -407,12 +408,12 @@ const api = {
|
|||
headers: headers || {},
|
||||
body,
|
||||
},
|
||||
}).json(),
|
||||
}).json<WebhookCreateApiResponse>(),
|
||||
});
|
||||
},
|
||||
useUpdateWebhook() {
|
||||
return useMutation<
|
||||
any,
|
||||
WebhookUpdateApiResponse,
|
||||
Error | HTTPError,
|
||||
{
|
||||
id: string;
|
||||
|
|
@ -434,15 +435,6 @@ const api = {
|
|||
queryParams,
|
||||
headers,
|
||||
body,
|
||||
}: {
|
||||
id: string;
|
||||
service: string;
|
||||
url: string;
|
||||
name: string;
|
||||
description: string;
|
||||
queryParams?: Record<string, string>;
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
}) =>
|
||||
hdxServer(`webhooks/${id}`, {
|
||||
method: 'PUT',
|
||||
|
|
@ -455,21 +447,25 @@ const api = {
|
|||
headers: headers || {},
|
||||
body,
|
||||
},
|
||||
}).json(),
|
||||
}).json<WebhookUpdateApiResponse>(),
|
||||
});
|
||||
},
|
||||
useWebhooks(services: string[]) {
|
||||
return useQuery<any, Error>({
|
||||
return useQuery<WebhooksApiResponse, Error>({
|
||||
queryKey: [...services],
|
||||
queryFn: () =>
|
||||
hdxServer('webhooks', {
|
||||
method: 'GET',
|
||||
searchParams: [...services.map(service => ['service', service])],
|
||||
}).json(),
|
||||
}).json<WebhooksApiResponse>(),
|
||||
});
|
||||
},
|
||||
useDeleteWebhook() {
|
||||
return useMutation<any, Error | HTTPError, { id: string }>({
|
||||
return useMutation<
|
||||
Record<string, never>,
|
||||
Error | HTTPError,
|
||||
{ id: string }
|
||||
>({
|
||||
mutationFn: async ({ id }: { id: string }) =>
|
||||
hdxServer(`webhooks/${id}`, {
|
||||
method: 'DELETE',
|
||||
|
|
@ -478,7 +474,7 @@ const api = {
|
|||
},
|
||||
useTestWebhook() {
|
||||
return useMutation<
|
||||
any,
|
||||
WebhookTestApiResponse,
|
||||
Error | HTTPError,
|
||||
{
|
||||
service: string;
|
||||
|
|
@ -488,19 +484,7 @@ const api = {
|
|||
body?: string;
|
||||
}
|
||||
>({
|
||||
mutationFn: async ({
|
||||
service,
|
||||
url,
|
||||
queryParams,
|
||||
headers,
|
||||
body,
|
||||
}: {
|
||||
service: string;
|
||||
url: string;
|
||||
queryParams?: Record<string, string>;
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
}) =>
|
||||
mutationFn: async ({ service, url, queryParams, headers, body }) =>
|
||||
hdxServer(`webhooks/test`, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
|
|
@ -510,20 +494,16 @@ const api = {
|
|||
headers: headers || {},
|
||||
body,
|
||||
},
|
||||
}).json(),
|
||||
}).json<WebhookTestApiResponse>(),
|
||||
});
|
||||
},
|
||||
useRegisterPassword() {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}) =>
|
||||
return useMutation<
|
||||
{ status: string },
|
||||
Error,
|
||||
{ email: string; password: string; confirmPassword: string }
|
||||
>({
|
||||
mutationFn: async ({ email, password, confirmPassword }) =>
|
||||
hdxServer(`register/password`, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
|
|
@ -531,20 +511,16 @@ const api = {
|
|||
password,
|
||||
confirmPassword,
|
||||
},
|
||||
}).json(),
|
||||
}).json<{ status: string }>(),
|
||||
});
|
||||
},
|
||||
useTestConnection() {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
}: {
|
||||
host: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}) =>
|
||||
return useMutation<
|
||||
{ success: boolean; error?: string },
|
||||
Error,
|
||||
{ host: string; username: string; password: string }
|
||||
>({
|
||||
mutationFn: async ({ host, username, password }) =>
|
||||
hdxServer(`clickhouse-proxy/test`, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
|
|
@ -552,7 +528,7 @@ const api = {
|
|||
username,
|
||||
password,
|
||||
},
|
||||
}).json() as Promise<{ success: boolean; error?: string }>,
|
||||
}).json<{ success: boolean; error?: string }>(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -899,11 +899,9 @@ export const RawLogTable = memo(
|
|||
isLast={isLast}
|
||||
onRemoveColumn={
|
||||
onRemoveColumn &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(header.column.columnDef.meta as any)?.column
|
||||
? () => {
|
||||
onRemoveColumn(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(header.column.columnDef.meta as any)
|
||||
?.column,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ export default function TeamMembersSection() {
|
|||
<Table.Tbody>
|
||||
{!isLoadingMembers &&
|
||||
Array.isArray(members?.data) &&
|
||||
members?.data.map((member: any) => (
|
||||
members?.data.map(member => (
|
||||
<Table.Tr key={member.email}>
|
||||
<Table.Td>
|
||||
<div>
|
||||
|
|
@ -284,8 +284,8 @@ export default function TeamMembersSection() {
|
|||
</Table.Tr>
|
||||
))}
|
||||
{!isLoadingInvitations &&
|
||||
Array.isArray(invitations.data) &&
|
||||
invitations.data.map((invitation: any) => (
|
||||
Array.isArray(invitations?.data) &&
|
||||
invitations.data.map(invitation => (
|
||||
<Table.Tr key={invitation.email} className="mt-2">
|
||||
<Table.Td>
|
||||
<span className="text-white fw-bold fs-7">
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export function useMetadataWithSettings() {
|
|||
useEffect(() => {
|
||||
if (me?.team?.metadataMaxRowsToRead && !settingsApplied.current) {
|
||||
metadata.setClickHouseSettings({
|
||||
max_rows_to_read: me.team.metadataMaxRowsToRead,
|
||||
max_rows_to_read: String(me.team.metadataMaxRowsToRead),
|
||||
});
|
||||
settingsApplied.current = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import {
|
||||
Alert,
|
||||
AlertHistory,
|
||||
AlertsPageItem as _AlertsPageItem,
|
||||
BuilderChartConfig,
|
||||
DashboardSchema,
|
||||
Filter,
|
||||
|
|
@ -36,16 +36,7 @@ export type LogStreamModel = KeyValuePairs & {
|
|||
trace_id?: string;
|
||||
};
|
||||
|
||||
export type AlertsPageItem = Alert & {
|
||||
_id: string;
|
||||
history: AlertHistory[];
|
||||
dashboard?: ServerDashboard;
|
||||
savedSearch?: SavedSearch;
|
||||
createdBy?: {
|
||||
email: string;
|
||||
name?: string;
|
||||
};
|
||||
};
|
||||
export type AlertsPageItem = _AlertsPageItem;
|
||||
|
||||
export type AlertWithCreatedBy = Alert & {
|
||||
createdBy?: {
|
||||
|
|
|
|||
|
|
@ -250,20 +250,22 @@ export enum WebhookService {
|
|||
IncidentIO = 'incidentio',
|
||||
}
|
||||
|
||||
// Base webhook interface (matches backend IWebhook but with JSON-serialized types)
|
||||
// Base webhook schema (matches backend IWebhook but with JSON-serialized types)
|
||||
// When making changes here, consider if they need to be made to the external API schema as well (packages/api/src/utils/zod.ts).
|
||||
export interface IWebhook {
|
||||
_id: string;
|
||||
createdAt: string;
|
||||
name: string;
|
||||
service: WebhookService;
|
||||
updatedAt: string;
|
||||
url?: string;
|
||||
description?: string;
|
||||
queryParams?: Record<string, string>;
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
}
|
||||
export const WebhookSchema = z.object({
|
||||
_id: z.string(),
|
||||
createdAt: z.string(),
|
||||
name: z.string(),
|
||||
service: z.nativeEnum(WebhookService),
|
||||
updatedAt: z.string(),
|
||||
url: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
queryParams: z.record(z.string(), z.string()).optional(),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
body: z.string().optional(),
|
||||
});
|
||||
|
||||
export type IWebhook = z.infer<typeof WebhookSchema>;
|
||||
|
||||
// Webhook API response type (excludes team field for security)
|
||||
export type WebhookApiData = Omit<IWebhook, 'team'>;
|
||||
|
|
@ -434,12 +436,14 @@ export const AlertSchema = z.union([
|
|||
|
||||
export type Alert = z.infer<typeof AlertSchema>;
|
||||
|
||||
export type AlertHistory = {
|
||||
counts: number;
|
||||
createdAt: string;
|
||||
lastValues: { startTime: string; count: number }[];
|
||||
state: AlertState;
|
||||
};
|
||||
export const AlertHistorySchema = z.object({
|
||||
counts: z.number(),
|
||||
createdAt: z.string(),
|
||||
lastValues: z.array(z.object({ startTime: z.string(), count: z.number() })),
|
||||
state: z.nativeEnum(AlertState),
|
||||
});
|
||||
|
||||
export type AlertHistory = z.infer<typeof AlertHistorySchema>;
|
||||
|
||||
// --------------------------
|
||||
// FILTERS
|
||||
|
|
@ -1119,3 +1123,200 @@ export const AssistantResponseConfig = z.discriminatedUnion('displayType', [
|
|||
export type AssistantResponseConfigSchema = z.infer<
|
||||
typeof AssistantResponseConfig
|
||||
>;
|
||||
|
||||
// --------------------------
|
||||
// API RESPONSE SCHEMAS
|
||||
// --------------------------
|
||||
|
||||
// Alerts
|
||||
export const AlertsPageItemSchema = z.object({
|
||||
_id: z.string(),
|
||||
interval: AlertIntervalSchema,
|
||||
scheduleOffsetMinutes: z.number().optional(),
|
||||
scheduleStartAt: z.union([z.string(), z.date()]).nullish(),
|
||||
threshold: z.number(),
|
||||
thresholdType: z.nativeEnum(AlertThresholdType),
|
||||
channel: z.object({ type: z.string().optional() }),
|
||||
state: z.nativeEnum(AlertState).optional(),
|
||||
source: z.nativeEnum(AlertSource).optional(),
|
||||
dashboardId: z.string().optional(),
|
||||
savedSearchId: z.string().optional(),
|
||||
tileId: z.string().optional(),
|
||||
name: z.string().nullish(),
|
||||
message: z.string().nullish(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
history: z.array(AlertHistorySchema),
|
||||
dashboard: z
|
||||
.object({
|
||||
_id: z.string(),
|
||||
name: z.string(),
|
||||
updatedAt: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
tiles: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
config: z.object({ name: z.string().optional() }),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.optional(),
|
||||
savedSearch: z
|
||||
.object({
|
||||
_id: z.string(),
|
||||
createdAt: z.string(),
|
||||
name: z.string(),
|
||||
updatedAt: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
})
|
||||
.optional(),
|
||||
createdBy: z
|
||||
.object({
|
||||
email: z.string(),
|
||||
name: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
silenced: z
|
||||
.object({
|
||||
by: z.string(),
|
||||
at: z.string(),
|
||||
until: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type AlertsPageItem = z.infer<typeof AlertsPageItemSchema>;
|
||||
|
||||
export const AlertsApiResponseSchema = z.object({
|
||||
data: z.array(AlertsPageItemSchema),
|
||||
});
|
||||
|
||||
export type AlertsApiResponse = z.infer<typeof AlertsApiResponseSchema>;
|
||||
|
||||
// Webhooks
|
||||
export const WebhooksApiResponseSchema = z.object({
|
||||
data: z.array(WebhookSchema),
|
||||
});
|
||||
|
||||
export type WebhooksApiResponse = z.infer<typeof WebhooksApiResponseSchema>;
|
||||
|
||||
export const WebhookCreateApiResponseSchema = z.object({
|
||||
data: WebhookSchema,
|
||||
});
|
||||
|
||||
export type WebhookCreateApiResponse = z.infer<
|
||||
typeof WebhookCreateApiResponseSchema
|
||||
>;
|
||||
|
||||
export const WebhookUpdateApiResponseSchema = z.object({
|
||||
data: WebhookSchema,
|
||||
});
|
||||
|
||||
export type WebhookUpdateApiResponse = z.infer<
|
||||
typeof WebhookUpdateApiResponseSchema
|
||||
>;
|
||||
|
||||
export const WebhookTestApiResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
export type WebhookTestApiResponse = z.infer<
|
||||
typeof WebhookTestApiResponseSchema
|
||||
>;
|
||||
|
||||
// Team
|
||||
export const TeamApiResponseSchema = z.object({
|
||||
_id: z.string(),
|
||||
allowedAuthMethods: z.array(z.literal('password')).optional(),
|
||||
apiKey: z.string(),
|
||||
name: z.string(),
|
||||
createdAt: z.string(),
|
||||
});
|
||||
|
||||
export type TeamApiResponse = z.infer<typeof TeamApiResponseSchema>;
|
||||
|
||||
export const TeamMemberSchema = z.object({
|
||||
_id: z.string(),
|
||||
email: z.string(),
|
||||
name: z.string().optional(),
|
||||
hasPasswordAuth: z.boolean(),
|
||||
isCurrentUser: z.boolean(),
|
||||
groupName: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TeamMember = z.infer<typeof TeamMemberSchema>;
|
||||
|
||||
export const TeamMembersApiResponseSchema = z.object({
|
||||
data: z.array(TeamMemberSchema),
|
||||
});
|
||||
|
||||
export type TeamMembersApiResponse = z.infer<
|
||||
typeof TeamMembersApiResponseSchema
|
||||
>;
|
||||
|
||||
export const TeamInvitationSchema = z.object({
|
||||
_id: z.string(),
|
||||
createdAt: z.string(),
|
||||
email: z.string(),
|
||||
name: z.string().optional(),
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type TeamInvitation = z.infer<typeof TeamInvitationSchema>;
|
||||
|
||||
export const TeamInvitationsApiResponseSchema = z.object({
|
||||
data: z.array(TeamInvitationSchema),
|
||||
});
|
||||
|
||||
export type TeamInvitationsApiResponse = z.infer<
|
||||
typeof TeamInvitationsApiResponseSchema
|
||||
>;
|
||||
|
||||
export const TeamTagsApiResponseSchema = z.object({
|
||||
data: z.array(z.string()),
|
||||
});
|
||||
|
||||
export type TeamTagsApiResponse = z.infer<typeof TeamTagsApiResponseSchema>;
|
||||
|
||||
export const UpdateClickHouseSettingsApiResponseSchema =
|
||||
TeamClickHouseSettingsSchema.partial();
|
||||
|
||||
export type UpdateClickHouseSettingsApiResponse = z.infer<
|
||||
typeof UpdateClickHouseSettingsApiResponseSchema
|
||||
>;
|
||||
|
||||
export const RotateApiKeyApiResponseSchema = z.object({
|
||||
newApiKey: z.string(),
|
||||
});
|
||||
|
||||
export type RotateApiKeyApiResponse = z.infer<
|
||||
typeof RotateApiKeyApiResponseSchema
|
||||
>;
|
||||
|
||||
// Installation
|
||||
export const InstallationApiResponseSchema = z.object({
|
||||
isTeamExisting: z.boolean(),
|
||||
});
|
||||
|
||||
export type InstallationApiResponse = z.infer<
|
||||
typeof InstallationApiResponseSchema
|
||||
>;
|
||||
|
||||
// Me
|
||||
export const MeApiResponseSchema = z.object({
|
||||
accessKey: z.string(),
|
||||
createdAt: z.string(),
|
||||
email: z.string(),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
team: TeamSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
allowedAuthMethods: true,
|
||||
apiKey: true,
|
||||
}).merge(TeamClickHouseSettingsSchema),
|
||||
usageStatsEnabled: z.boolean(),
|
||||
aiAssistantEnabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type MeApiResponse = z.infer<typeof MeApiResponseSchema>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue