api: add first version of export api
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
7ea95afe16
commit
7f8d857508
88
apps/api/src/controllers/export.controller.ts
Normal file
88
apps/api/src/controllers/export.controller.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
|
||||
import type { GetEventListOptions } from '@openpanel/db';
|
||||
import { ClientType, db, getEventList, getEventsCount } from '@openpanel/db';
|
||||
|
||||
type EventsQuery = {
|
||||
project_id?: string;
|
||||
event?: string | string[];
|
||||
start?: string;
|
||||
end?: string;
|
||||
page?: string;
|
||||
};
|
||||
export async function events(
|
||||
request: FastifyRequest<{
|
||||
Querystring: EventsQuery;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const query = request.query;
|
||||
|
||||
if (query.project_id) {
|
||||
if (
|
||||
request.client?.type === ClientType.read &&
|
||||
request.client?.projectId !== query.project_id
|
||||
) {
|
||||
reply.status(403).send({
|
||||
error: 'Forbidden',
|
||||
message: 'You do not have access to this project',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const project = await db.project.findUnique({
|
||||
where: {
|
||||
organizationSlug: request.client?.organizationSlug,
|
||||
id: query.project_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
reply.status(404).send({
|
||||
error: 'Not Found',
|
||||
message: 'Project not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const projectId = query.project_id ?? request.client?.projectId;
|
||||
|
||||
if (!projectId) {
|
||||
reply.status(400).send({
|
||||
error: 'Bad Request',
|
||||
message: 'project_id is required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const cursor = (parseInt(query.page || '1', 10) || 1) - 1;
|
||||
const options: GetEventListOptions = {
|
||||
projectId,
|
||||
events: (Array.isArray(query.event) ? query.event : [query.event]).filter(
|
||||
(s): s is string => typeof s === 'string'
|
||||
),
|
||||
startDate: query.start ? new Date(query.start) : undefined,
|
||||
endDate: query.end ? new Date(query.end) : undefined,
|
||||
cursor,
|
||||
take: 50,
|
||||
meta: false,
|
||||
profile: true,
|
||||
};
|
||||
|
||||
const [data, totalCount] = await Promise.all([
|
||||
getEventList(options),
|
||||
getEventsCount(options),
|
||||
]);
|
||||
|
||||
reply.send({
|
||||
meta: {
|
||||
// options,
|
||||
count: data.length,
|
||||
totalCount: totalCount,
|
||||
pages: Math.ceil(totalCount / options.take),
|
||||
current: cursor + 1,
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import cors from '@fastify/cors';
|
||||
import Fastify from 'fastify';
|
||||
|
||||
import type { IServiceClient } from '@openpanel/db';
|
||||
import { redisPub } from '@openpanel/redis';
|
||||
|
||||
import eventRouter from './routes/event.router';
|
||||
import exportRouter from './routes/export.router';
|
||||
import liveRouter from './routes/live.router';
|
||||
import miscRouter from './routes/misc.router';
|
||||
import profileRouter from './routes/profile.router';
|
||||
@@ -12,6 +14,7 @@ import { logger, logInfo } from './utils/logger';
|
||||
declare module 'fastify' {
|
||||
interface FastifyRequest {
|
||||
projectId: string;
|
||||
client: IServiceClient | null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +36,7 @@ const startServer = async () => {
|
||||
fastify.register(profileRouter, { prefix: '/profile' });
|
||||
fastify.register(liveRouter, { prefix: '/live' });
|
||||
fastify.register(miscRouter, { prefix: '/misc' });
|
||||
fastify.register(exportRouter, { prefix: '/export' });
|
||||
fastify.setErrorHandler((error) => {
|
||||
fastify.log.error(error);
|
||||
});
|
||||
|
||||
37
apps/api/src/routes/export.router.ts
Normal file
37
apps/api/src/routes/export.router.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as controller from '@/controllers/export.controller';
|
||||
import { validateExportRequest } from '@/utils/auth';
|
||||
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||
|
||||
import { Prisma } from '@openpanel/db';
|
||||
|
||||
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||
fastify.addHook('preHandler', async (req: FastifyRequest, reply) => {
|
||||
try {
|
||||
const client = await validateExportRequest(req.headers);
|
||||
req.client = client;
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return reply.status(401).send({
|
||||
error: 'Unauthorized',
|
||||
message: 'Client ID seems to be malformed',
|
||||
});
|
||||
} else if (e instanceof Error) {
|
||||
return reply
|
||||
.status(401)
|
||||
.send({ error: 'Unauthorized', message: e.message });
|
||||
}
|
||||
return reply
|
||||
.status(401)
|
||||
.send({ error: 'Unauthorized', message: 'Unexpected error' });
|
||||
}
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/events',
|
||||
handler: controller.events,
|
||||
});
|
||||
done();
|
||||
};
|
||||
|
||||
export default eventRouter;
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RawRequestDefaultExpression } from 'fastify';
|
||||
|
||||
import { verifyPassword } from '@openpanel/common';
|
||||
import { db } from '@openpanel/db';
|
||||
import type { IServiceClient } from '@openpanel/db';
|
||||
import { ClientType, db } from '@openpanel/db';
|
||||
|
||||
import { logger } from './logger';
|
||||
|
||||
@@ -89,5 +90,39 @@ export async function validateSdkRequest(
|
||||
}
|
||||
}
|
||||
|
||||
if (!client.projectId) {
|
||||
throw new Error('No project id found for client');
|
||||
}
|
||||
|
||||
return client.projectId;
|
||||
}
|
||||
|
||||
export async function validateExportRequest(
|
||||
headers: RawRequestDefaultExpression['headers']
|
||||
): Promise<IServiceClient> {
|
||||
const clientId = headers['openpanel-client-id'] as string;
|
||||
const clientSecret = (headers['openpanel-client-secret'] as string) || '';
|
||||
const client = await db.client.findUnique({
|
||||
where: {
|
||||
id: clientId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!client) {
|
||||
throw new Error('Export: Invalid client id');
|
||||
}
|
||||
|
||||
if (!client.secret) {
|
||||
throw new Error('Export: Client has no secret');
|
||||
}
|
||||
|
||||
if (client.type === ClientType.write) {
|
||||
throw new Error('Export: Client is not allowed to export');
|
||||
}
|
||||
|
||||
if (!(await verifyPassword(clientSecret, client.secret))) {
|
||||
throw new Error('Export: Invalid client secret');
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user