0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-17 22:31:28 -05:00

feat(core): get /logs (#823)

* feat(core): get /logs

* chore(core): rename userRequest to logRequest in UTs
This commit is contained in:
IceHe.xyz 2022-05-16 14:43:33 +08:00 committed by GitHub
parent 82c7138683
commit 4ffd4c0480
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 1 deletions

View file

@ -1,5 +1,46 @@
import { CreateLog, Logs } from '@logto/schemas';
import { CreateLog, Log, Logs } from '@logto/schemas';
import { sql } from 'slonik';
import { buildInsertInto } from '@/database/insert-into';
import { conditionalSql, convertToIdentifiers } from '@/database/utils';
import envSet from '@/env-set';
const { table, fields } = convertToIdentifiers(Logs);
export const insertLog = buildInsertInto<CreateLog>(Logs);
export interface LogCondition {
logType?: string;
applicationId?: string;
userId?: string;
}
const buildLogConditionSql = (logCondition: LogCondition) =>
conditionalSql(logCondition, ({ logType, applicationId, userId }) => {
const subConditions = [
conditionalSql(logType, (logType) => sql`${fields.type}=${logType}`),
conditionalSql(userId, (userId) => sql`${fields.payload}->>'userId'=${userId}`),
conditionalSql(
applicationId,
(applicationId) => sql`${fields.payload}->>'applicationId'=${applicationId}`
),
].filter(({ sql }) => sql);
return sql`where ${sql.join(subConditions, sql` and `)}`;
});
export const countLogs = async (condition: LogCondition) =>
envSet.pool.one<{ count: number }>(sql`
select count(*)
from ${table}
${buildLogConditionSql(condition)}
`);
export const findLogs = async (limit: number, offset: number, logCondition: LogCondition) =>
envSet.pool.any<Log>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
${buildLogConditionSql(logCondition)}
limit ${limit}
offset ${offset}
`);

View file

@ -15,6 +15,7 @@ import statusRoutes from '@/routes/status';
import swaggerRoutes from '@/routes/swagger';
import adminUserRoutes from './admin-user';
import logRoutes from './log';
import roleRoutes from './role';
import { AnonymousRouter, AuthedRouter } from './types';
@ -35,6 +36,7 @@ const createRouters = (provider: Provider) => {
resourceRoutes(authedRouter);
signInExperiencesRoutes(authedRouter);
adminUserRoutes(authedRouter);
logRoutes(authedRouter);
roleRoutes(authedRouter);
return [sessionRouter, anonymousRouter, authedRouter];

View file

@ -0,0 +1,51 @@
import { LogCondition } from '@/queries/log';
import logRoutes from '@/routes/log';
import { createRequester } from '@/utils/test-utils';
const mockLogs = [{ id: 1 }, { id: 2 }];
/* eslint-disable @typescript-eslint/no-unused-vars */
const countLogs = jest.fn(async (condition: LogCondition) => ({
count: mockLogs.length,
}));
const findLogs = jest.fn(
async (limit: number, offset: number, condition: LogCondition) => mockLogs
);
/* eslint-enable @typescript-eslint/no-unused-vars */
jest.mock('@/queries/log', () => ({
countLogs: async (condition: LogCondition) => countLogs(condition),
findLogs: async (limit: number, offset: number, condition: LogCondition) =>
findLogs(limit, offset, condition),
}));
describe('logRoutes', () => {
const logRequest = createRequester({ authedRoutes: logRoutes });
afterEach(() => {
jest.clearAllMocks();
});
describe('GET /logs', () => {
it('should call countLogs and findLogs with correct parameters', async () => {
const userId = 'userIdValue';
const applicationId = 'foo';
const logType = 'SignInUsernamePassword';
const page = 1;
const pageSize = 5;
await logRequest.get(
`/logs?userId=${userId}&applicationId=${applicationId}&logType=${logType}&page=${page}&page_size=${pageSize}`
);
expect(countLogs).toHaveBeenCalledWith({ userId, applicationId, logType });
expect(findLogs).toHaveBeenCalledWith(5, 0, { userId, applicationId, logType });
});
it('should return correct response', async () => {
const response = await logRequest.get(`/logs`);
expect(response.status).toEqual(200);
expect(response.body).toEqual(mockLogs);
expect(response.header).toHaveProperty('total-number', `${mockLogs.length}`);
});
});
});

View file

@ -0,0 +1,38 @@
import { object, string } from 'zod';
import koaGuard from '@/middleware/koa-guard';
import koaPagination from '@/middleware/koa-pagination';
import { countLogs, findLogs } from '@/queries/log';
import { AuthedRouter } from './types';
export default function logRoutes<T extends AuthedRouter>(router: T) {
router.get(
'/logs',
koaPagination(),
koaGuard({
query: object({
userId: string().optional(),
applicationId: string().optional(),
logType: string().optional(),
}),
}),
async (ctx, next) => {
const { limit, offset } = ctx.pagination;
const {
query: { userId, applicationId, logType },
} = ctx.guard;
const [{ count }, logs] = await Promise.all([
countLogs({ logType, applicationId, userId }),
findLogs(limit, offset, { logType, userId, applicationId }),
]);
// Return totalCount to pagination middleware
ctx.pagination.totalCount = count;
ctx.body = logs;
return next();
}
);
}