Skip to content

Commit

Permalink
feat: Use TypeBox for Fastify schema definitions
Browse files Browse the repository at this point in the history
Switched to using TypeBox due to TS errors when using JsonSchema.
TypeBox can also be used for defining Mongoose types to reduce duplication.
  • Loading branch information
jrassa committed Dec 6, 2024
1 parent 492892c commit 05c6067
Show file tree
Hide file tree
Showing 39 changed files with 730 additions and 698 deletions.
72 changes: 24 additions & 48 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"@fastify/session": "^11.0.1",
"@fastify/swagger": "^9.2.0",
"@fastify/swagger-ui": "^5.1.0",
"@fastify/type-provider-json-schema-to-ts": "^4.0.1",
"@fastify/type-provider-typebox": "^5.0.1",
"@sinclair/typebox": "^0.33.22",
"agenda": "^4.3.0",
"config": "^3.3.11",
"connect-mongo": "^5.1.0",
Expand Down
30 changes: 11 additions & 19 deletions src/app/core/access-checker/access-checker.controller.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts';
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { FastifyInstance } from 'fastify';

import accessCheckerService from './access-checker.service';
import cacheEntryService from './cache/cache-entry.service';
import { PagingQueryStringSchema, SearchBodySchema } from '../core.schemas';
import { PagingQueryStringType, SearchBodyType } from '../core.types';
import { requireAdminAccess, requireLogin } from '../user/auth/auth.hooks';

const KeyParamsType = Type.Object({
key: Type.String()
});

export default function (_fastify: FastifyInstance) {
const fastify = _fastify.withTypeProvider<JsonSchemaToTsProvider>();
const fastify = _fastify.withTypeProvider<TypeBoxTypeProvider>();
fastify.route({
method: 'POST',
url: '/access-checker/entry/:key',
schema: {
description: 'Trigger cache entry refresh',
tags: ['Access Checker'],
params: {
type: 'object',
properties: {
key: { type: 'string' }
},
required: ['key']
}
params: KeyParamsType
},
preValidation: requireAdminAccess,
handler: async function (req, reply) {
Expand All @@ -35,13 +33,7 @@ export default function (_fastify: FastifyInstance) {
schema: {
description: 'Delete cache entry',
tags: ['Access Checker'],
params: {
type: 'object',
properties: {
key: { type: 'string' }
},
required: ['key']
}
params: KeyParamsType
},
preValidation: requireAdminAccess,
handler: async function (req, reply) {
Expand All @@ -56,8 +48,8 @@ export default function (_fastify: FastifyInstance) {
schema: {
description: 'Search cache entries',
tags: ['Access Checker'],
body: SearchBodySchema,
querystring: PagingQueryStringSchema
body: SearchBodyType,
querystring: PagingQueryStringType
},
preValidation: requireAdminAccess,
handler: async function (req, reply) {
Expand Down
20 changes: 12 additions & 8 deletions src/app/core/access-checker/cache/cache-entry.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Schema, model, HydratedDocument, Model, Types } from 'mongoose';
import { Static, Type } from '@fastify/type-provider-typebox';
import { Schema, model, HydratedDocument, Model } from 'mongoose';

import {
ContainsSearchable,
Expand All @@ -9,14 +10,17 @@ import {
paginatePlugin,
Paginateable
} from '../../../common/mongoose/paginate.plugin';
import { DateTimeType, ObjectIdType } from '../../core.types';

export const CacheEntryType = Type.Object({
_id: ObjectIdType,
key: Type.String(),
ts: DateTimeType,
value: Type.Record(Type.String(), Type.Unknown()),
valueString: Type.String()
});

export interface ICacheEntry {
_id: Types.ObjectId;
key: string;
ts: Date;
value: Record<string, unknown>;
valueString: string;
}
export type ICacheEntry = Static<typeof CacheEntryType>;

export interface ICacheEntryMethods {
fullCopy(): Record<string, unknown>;
Expand Down
39 changes: 25 additions & 14 deletions src/app/core/audit/audit.controller.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts';
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { FastifyInstance } from 'fastify';
import _ from 'lodash';
import { FilterQuery } from 'mongoose';

import { Audit, AuditDocument } from './audit.model';
import { Audit, AuditDocument, AuditType } from './audit.model';
import { config, utilService as util } from '../../../dependencies';
import { PagingQueryStringSchema, SearchBodySchema } from '../core.schemas';
import {
PagingQueryStringType,
PagingResultsType,
SearchBodyType
} from '../core.types';
import { Callbacks } from '../export/callbacks';
import * as exportConfigController from '../export/export-config.controller';
import { loadExportConfigById } from '../export/export-config.controller';
import { requireAuditorAccess } from '../user/auth/auth.hooks';

export default function (_fastify: FastifyInstance) {
const fastify = _fastify.withTypeProvider<JsonSchemaToTsProvider>();
const fastify = _fastify.withTypeProvider<TypeBoxTypeProvider>();
fastify.route({
method: 'GET',
url: '/audit/distinctValues',
schema: {
description:
'Retrieves the distinct values for a field in the Audit collection',
tags: ['Audit'],
querystring: {
type: 'object',
properties: {
field: { type: 'string' }
},
required: ['field']
}
hide: true,
querystring: Type.Object({
field: Type.String()
})
},
preValidation: requireAuditorAccess,
handler: async function (req, reply) {
Expand All @@ -41,8 +42,12 @@ export default function (_fastify: FastifyInstance) {
schema: {
description: 'Returns audit records matching search criteria',
tags: ['Audit'],
body: SearchBodySchema,
querystring: PagingQueryStringSchema
hide: true,
body: SearchBodyType,
querystring: PagingQueryStringType,
response: {
200: PagingResultsType(AuditType)
}
},
preValidation: requireAuditorAccess,
handler: async function (req, reply) {
Expand Down Expand Up @@ -84,7 +89,11 @@ export default function (_fastify: FastifyInstance) {
url: '/audit/csv/:id',
schema: {
description: 'Export audit records as CSV file',
tags: ['Audit']
tags: ['Audit'],
hide: true,
params: Type.Object({
id: Type.String()
})
},
preValidation: requireAuditorAccess,
preHandler: loadExportConfigById,
Expand Down Expand Up @@ -121,6 +130,8 @@ export default function (_fastify: FastifyInstance) {
.cursor();

exportConfigController.exportCSV(req, reply, fileName, columns, cursor);

return reply;
}
});
}
37 changes: 22 additions & 15 deletions src/app/core/audit/audit.model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Static, Type } from '@fastify/type-provider-typebox';
import config from 'config';
import { HydratedDocument, Model, model, Schema } from 'mongoose';

Expand All @@ -10,22 +11,28 @@ import {
Paginateable,
paginatePlugin
} from '../../common/mongoose/paginate.plugin';
import { DateTimeType } from '../core.types';

export const AuditType = Type.Object({
created: DateTimeType,
message: Type.String(),
audit: Type.Object({
auditType: Type.String(),
action: Type.String(),
actor: Type.Record(Type.String(), Type.Unknown()),
object: Type.Union([
Type.String(),
Type.Record(Type.String(), Type.Unknown())
]),
userSpec: Type.Object({
browser: Type.String(),
os: Type.String()
}),
masqueradingUser: Type.Optional(Type.String())
})
});

interface IAudit {
created: Date;
message: string;
audit: {
auditType: string;
action: string;
actor: Record<string, unknown>;
object: string | Record<string, unknown>;
userSpec: {
browser: string;
os: string;
};
masqueradingUser?: string;
};
}
type IAudit = Static<typeof AuditType>;

export type AuditDocument = HydratedDocument<
IAudit,
Expand Down
Loading

0 comments on commit 05c6067

Please sign in to comment.