mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
add api support
This commit is contained in:
27
dashboard/server/api/v1/events.post.ts
Normal file
27
dashboard/server/api/v1/events.post.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
import { checkApiKey, checkAuthorization, eventsListApi } from '~/server/services/ApiService';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const { rows, from, to, limit } = await readBody(event);
|
||||||
|
|
||||||
|
const token = checkAuthorization(event);
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
const apiKeyResult = await checkApiKey(token);
|
||||||
|
if (!apiKeyResult.ok) return setResponseStatus(event, 401, 'ApiKey not valid');
|
||||||
|
|
||||||
|
if (!rows) return setResponseStatus(event, 400, 'rows is required');
|
||||||
|
if (!Array.isArray(rows)) return setResponseStatus(event, 400, 'rows must be an array');
|
||||||
|
if (rows.length == 0) return setResponseStatus(event, 400, 'rows cannot be empty');
|
||||||
|
|
||||||
|
if (Array.isArray(from)) return setResponseStatus(event, 400, 'Only one "from" is allowed');
|
||||||
|
if (Array.isArray(to)) return setResponseStatus(event, 400, 'Only one "to" is allowed');
|
||||||
|
|
||||||
|
const result = await eventsListApi(apiKeyResult.data.apiKey, apiKeyResult.data.project_id.toString(), rows, limit as string, from as string, to as string);
|
||||||
|
|
||||||
|
if (result.ok) return result;
|
||||||
|
return setResponseStatus(event, result.code, result.error);
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,44 +1,25 @@
|
|||||||
|
|
||||||
import { ApiSettingsModel } from '@schema/ApiSettingsSchema';
|
import { checkApiKey, checkAuthorization, eventsListApi, } from '~/server/services/ApiService';
|
||||||
import { EventModel } from '@schema/metrics/EventSchema';
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const { row, from, to, limit } = getQuery(event);
|
const { row, from, to, limit } = getQuery(event);
|
||||||
|
|
||||||
const authorization = getHeader(event, 'Authorization');
|
const token = checkAuthorization(event);
|
||||||
if (!authorization) return setResponseStatus(event, 403, 'Authorization is required');
|
if (!token) return;
|
||||||
|
|
||||||
const [type, token] = authorization.split(' ');
|
const apiKeyResult = await checkApiKey(token);
|
||||||
if (type != 'Bearer') return setResponseStatus(event, 401, 'Malformed authorization');
|
if (!apiKeyResult.ok) return setResponseStatus(event, 401, 'ApiKey not valid');
|
||||||
|
|
||||||
const apiSettings = await ApiSettingsModel.findOne({ apiKey: token });
|
|
||||||
if (!apiSettings) return setResponseStatus(event, 401, 'ApiKey not valid');
|
|
||||||
|
|
||||||
if (!row) return setResponseStatus(event, 400, 'row is required');
|
|
||||||
|
|
||||||
|
if (Array.isArray(from)) return setResponseStatus(event, 400, 'Only one "from" is allowed');
|
||||||
|
if (Array.isArray(to)) return setResponseStatus(event, 400, 'Only one "to" is allowed');
|
||||||
|
|
||||||
const rows: string[] = Array.isArray(row) ? row as string[] : [row as string];
|
const rows: string[] = Array.isArray(row) ? row as string[] : [row as string];
|
||||||
|
|
||||||
const projection: any = {};
|
const result = await eventsListApi(apiKeyResult.data.apiKey, apiKeyResult.data.project_id.toString(), rows, limit as string, from as string, to as string);
|
||||||
|
|
||||||
for (const row of rows) {
|
|
||||||
projection[row] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const limitNumber = parseInt((limit as string));
|
|
||||||
|
|
||||||
const limitValue = isNaN(limitNumber) ? 100 : limitNumber;
|
|
||||||
|
|
||||||
const visits = await EventModel.find({
|
|
||||||
project_id: apiSettings.project_id,
|
|
||||||
created_at: {
|
|
||||||
$gte: from || new Date(2023, 0),
|
|
||||||
$lte: to || new Date(3000, 0)
|
|
||||||
}
|
|
||||||
}, { _id: 0, ...projection }, { limit: limitValue });
|
|
||||||
|
|
||||||
return visits.map(e => e.toJSON());
|
|
||||||
|
|
||||||
|
if (result.ok) return result;
|
||||||
|
return setResponseStatus(event, result.code, result.error);
|
||||||
|
|
||||||
});
|
});
|
||||||
28
dashboard/server/api/v1/visits.post.ts
Normal file
28
dashboard/server/api/v1/visits.post.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
import { checkApiKey, checkAuthorization } from '~/server/services/ApiService';
|
||||||
|
import { visitsListApi } from '../../services/ApiService';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const { rows, from, to, limit } = await readBody(event);
|
||||||
|
|
||||||
|
const token = checkAuthorization(event);
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
const apiKeyResult = await checkApiKey(token);
|
||||||
|
if (!apiKeyResult.ok) return setResponseStatus(event, 401, 'ApiKey not valid');
|
||||||
|
|
||||||
|
if (!rows) return setResponseStatus(event, 400, 'rows is required');
|
||||||
|
if (!Array.isArray(rows)) return setResponseStatus(event, 400, 'rows must be an array');
|
||||||
|
if (rows.length == 0) return setResponseStatus(event, 400, 'rows cannot be empty');
|
||||||
|
|
||||||
|
if (Array.isArray(from)) return setResponseStatus(event, 400, 'Only one "from" is allowed');
|
||||||
|
if (Array.isArray(to)) return setResponseStatus(event, 400, 'Only one "to" is allowed');
|
||||||
|
|
||||||
|
const result = await visitsListApi(apiKeyResult.data.apiKey, apiKeyResult.data.project_id.toString(), rows, limit as string, from as string, to as string);
|
||||||
|
|
||||||
|
if (result.ok) return result;
|
||||||
|
return setResponseStatus(event, result.code, result.error);
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,44 +1,27 @@
|
|||||||
|
|
||||||
import { ApiSettingsModel } from '@schema/ApiSettingsSchema';
|
import { ApiSettingsModel } from '@schema/ApiSettingsSchema';
|
||||||
import { VisitModel } from '@schema/metrics/VisitSchema';
|
import { VisitModel } from '@schema/metrics/VisitSchema';
|
||||||
|
import { checkApiKey, checkAuthorization, visitsListApi } from '~/server/services/ApiService';
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const { row, from, to, limit } = getQuery(event);
|
const { row, from, to, limit } = getQuery(event);
|
||||||
|
|
||||||
const authorization = getHeader(event, 'Authorization');
|
const token = checkAuthorization(event);
|
||||||
if (!authorization) return setResponseStatus(event, 403, 'Authorization is required');
|
if (!token) return;
|
||||||
|
|
||||||
const [type, token] = authorization.split(' ');
|
const apiKeyResult = await checkApiKey(token);
|
||||||
if (type != 'Bearer') return setResponseStatus(event, 401, 'Malformed authorization');
|
if (!apiKeyResult.ok) return setResponseStatus(event, 401, 'ApiKey not valid');
|
||||||
|
|
||||||
const apiSettings = await ApiSettingsModel.findOne({ apiKey: token });
|
|
||||||
if (!apiSettings) return setResponseStatus(event, 401, 'ApiKey not valid');
|
|
||||||
|
|
||||||
if (!row) return setResponseStatus(event, 400, 'row is required');
|
|
||||||
|
|
||||||
|
if (Array.isArray(from)) return setResponseStatus(event, 400, 'Only one "from" is allowed');
|
||||||
|
if (Array.isArray(to)) return setResponseStatus(event, 400, 'Only one "to" is allowed');
|
||||||
|
|
||||||
const rows: string[] = Array.isArray(row) ? row as string[] : [row as string];
|
const rows: string[] = Array.isArray(row) ? row as string[] : [row as string];
|
||||||
|
|
||||||
const projection: any = {};
|
const result = await visitsListApi(apiKeyResult.data.apiKey, apiKeyResult.data.project_id.toString(), rows, limit as string, from as string, to as string);
|
||||||
|
|
||||||
for (const row of rows) {
|
if (result.ok) return result;
|
||||||
projection[row] = 1;
|
return setResponseStatus(event, result.code, result.error);
|
||||||
}
|
|
||||||
|
|
||||||
const limitNumber = parseInt((limit as string));
|
|
||||||
|
|
||||||
const limitValue = isNaN(limitNumber) ? 100 : limitNumber;
|
|
||||||
|
|
||||||
const visits = await VisitModel.find({
|
|
||||||
project_id: apiSettings.project_id,
|
|
||||||
created_at: {
|
|
||||||
$gte: from || new Date(2023, 0),
|
|
||||||
$lte: to || new Date(3000, 0)
|
|
||||||
}
|
|
||||||
}, { _id: 0, ...projection }, { limit: limitValue });
|
|
||||||
|
|
||||||
return visits.map(e => e.toJSON());
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
86
dashboard/server/services/ApiService.ts
Normal file
86
dashboard/server/services/ApiService.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
import { ApiSettingsModel, TApiSettings } from '@schema/ApiSettingsSchema';
|
||||||
|
import { EventModel } from '@schema/metrics/EventSchema';
|
||||||
|
import { VisitModel } from '@schema/metrics/VisitSchema';
|
||||||
|
import type { H3Event, EventHandlerRequest } from 'h3'
|
||||||
|
|
||||||
|
export function checkAuthorization(event: H3Event<EventHandlerRequest>) {
|
||||||
|
const authorization = getHeader(event, 'Authorization');
|
||||||
|
if (!authorization) return setResponseStatus(event, 403, 'Authorization is required');
|
||||||
|
|
||||||
|
const [type, token] = authorization.split(' ');
|
||||||
|
if (type != 'Bearer') return setResponseStatus(event, 401, 'Malformed authorization');
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckApiKeyResult = { ok: false } | { ok: true, data: TApiSettings };
|
||||||
|
|
||||||
|
export async function checkApiKey(apiKey: string): Promise<CheckApiKeyResult> {
|
||||||
|
const apiSettings = await ApiSettingsModel.findOne({ apiKey });
|
||||||
|
if (!apiSettings) return { ok: false }
|
||||||
|
return { ok: true, data: apiSettings }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function incrementApiUsage(apiKey: string, value: number) {
|
||||||
|
await ApiSettingsModel.updateOne({ apiKey }, { $inc: { usage: value } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkApiUsage(apiKey: string) {
|
||||||
|
const data = await ApiSettingsModel.findOne({ apiKey }, { usage: 1 });
|
||||||
|
if (!data) return false;
|
||||||
|
if (data.usage > 100000) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiResult = { ok: true, data: any } | { ok: false, code: number, error: string }
|
||||||
|
|
||||||
|
export async function eventsListApi(apiKey: string, project_id: string, rows: string[], limit?: number | string, from?: string, to?: string): Promise<ApiResult> {
|
||||||
|
|
||||||
|
const canMakeRequest = await checkApiUsage(apiKey);
|
||||||
|
|
||||||
|
if (!canMakeRequest) return { ok: false, code: 429, error: 'Api limit reached (100.000)' }
|
||||||
|
|
||||||
|
const projection = Object.fromEntries(rows.map(e => [e, 1]));
|
||||||
|
|
||||||
|
const limitNumber = parseInt((limit?.toString() as string));
|
||||||
|
const limitValue = isNaN(limitNumber) ? 100 : limitNumber;
|
||||||
|
|
||||||
|
const events = await EventModel.find({
|
||||||
|
project_id,
|
||||||
|
created_at: {
|
||||||
|
$gte: from || new Date(2023, 0),
|
||||||
|
$lte: to || new Date(3000, 0)
|
||||||
|
}
|
||||||
|
}, { _id: 0, ...projection }, { limit: limitValue });
|
||||||
|
|
||||||
|
await incrementApiUsage(apiKey, events.length);
|
||||||
|
|
||||||
|
return { ok: true, data: events.map(e => e.toJSON()) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function visitsListApi(apiKey: string, project_id: string, rows: string[], limit?: number | string, from?: string, to?: string): Promise<ApiResult> {
|
||||||
|
|
||||||
|
const canMakeRequest = await checkApiUsage(apiKey);
|
||||||
|
|
||||||
|
if (!canMakeRequest) return { ok: false, code: 429, error: 'Api limit reached (100.000)' }
|
||||||
|
|
||||||
|
const projection = Object.fromEntries(rows.map(e => [e, 1]));
|
||||||
|
|
||||||
|
const limitNumber = parseInt((limit?.toString() as string));
|
||||||
|
const limitValue = isNaN(limitNumber) ? 100 : limitNumber;
|
||||||
|
|
||||||
|
const visits = await VisitModel.find({
|
||||||
|
project_id,
|
||||||
|
created_at: {
|
||||||
|
$gte: from || new Date(2023, 0),
|
||||||
|
$lte: to || new Date(3000, 0)
|
||||||
|
}
|
||||||
|
}, { _id: 0, ...projection }, { limit: limitValue });
|
||||||
|
|
||||||
|
await incrementApiUsage(apiKey, visits.length);
|
||||||
|
|
||||||
|
return { ok: true, data: visits.map(e => e.toJSON()) };
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user