Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Schema Definition in TypeScript with Zod #181

Merged
merged 33 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bcc77c9
Create health router
joneubank Jun 23, 2023
8c9978d
Dictionary Routes moved into router
joneubank Jun 23, 2023
a93cffa
Comments and minor changes
joneubank Jun 23, 2023
01120c0
Refactor clients for external services into `/external` directory
joneubank Jun 23, 2023
abe3f78
Enforce TS strict and fix type issues
joneubank Jun 23, 2023
981f4a8
Major version bump, add immer and zod
joneubank Jun 24, 2023
c686bc9
Remove explicit paths declaration and rules redundant with strict
joneubank Jun 24, 2023
3c27775
Copyright year update
joneubank Jun 24, 2023
599822e
Dictionary Schema defined with Zod
joneubank Jun 24, 2023
9e29b36
Move dictionary model to `/db` dir and base off new types
joneubank Jun 24, 2023
b2e30dc
Move diff code to a service
joneubank Jun 24, 2023
6462db5
Clean types, validate request inputs
joneubank Jun 24, 2023
12b055a
Update types and db code reference
joneubank Jun 24, 2023
a6d17d0
Move express handler wrappers to /routers dir
joneubank Jun 24, 2023
c371a7a
Clean up types, use immer for immutable changes
joneubank Jun 24, 2023
53ac45a
Handle ZodErrors thrown during requests
joneubank Jun 24, 2023
ea21e9d
Use new types
joneubank Jun 24, 2023
38c26d6
Put `/diff` first to ensure it does not get blocked by `/:id` requests
joneubank Jun 24, 2023
f4b1f88
Small type fixes
joneubank Jun 24, 2023
39d40d7
Update tests and fixtures to work with latest type validations
joneubank Jun 24, 2023
b4cbb0e
Provide some basic schema examples
joneubank Jun 24, 2023
895f54f
Remove unused meta-schema
joneubank Jun 24, 2023
d92ee39
Move /diff to its own router
joneubank Jun 24, 2023
ffbb2af
Organize all imports
joneubank Jun 24, 2023
bdcbbbf
Diff request path back to original
joneubank Jun 24, 2023
bbfcee3
Organize Imports
joneubank Jun 24, 2023
bbc0f8f
Mocha Testing Config separate from npm scripts
joneubank Jul 4, 2023
a5abccb
Explicitly set `noImplicitAny` to enforce array.map type inference on…
joneubank Jul 4, 2023
b21b351
Move Reference Types to their own file
joneubank Jul 4, 2023
dfac2f4
Refactor references utilities to use proper reference types
joneubank Jul 4, 2023
66867fc
Move reference test fixtures and fix linebreak normalization tests
joneubank Jul 4, 2023
b602012
Proper formatting for beta version
joneubank Jul 5, 2023
6e8bae1
Comment clarification and typo correction
joneubank Jul 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"spec": "test/**/*.ts",
"timeout": 35000
}
16,175 changes: 8,097 additions & 8,078 deletions package-lock.json

Large diffs are not rendered by default.

150 changes: 76 additions & 74 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,76 +1,78 @@
{
"name": "lectern",
"version": "1.14.0",
"description": "Data Dictionary Management",
"scripts": {
"start": "npm run serve",
"build": "npm run build-ts",
"serve": "node dist/server.js",
"watch-node": "nodemon dist/server.js",
"test": "mocha --timeout 35000 --exit -r ts-node/register test/**/*.ts",
"watch-test": "mocha --watch --timeout 35000 --exit -r ts-node/register test/**/*.ts",
"build-ts": "tsc",
"watch-ts": "tsc -w",
"debug": "npm run build && npm run watch-debug",
"serve-debug": "nodemon --inspect dist/server.js",
"watch-debug": "nodemon --watch 'src/**/*.ts' --ignore 'src/**/*.spec.ts' --exec node --inspect-brk -r ts-node/register ./src/server.ts",
"gitmoji-config": "gitmoji -g",
"commit": "gitmoji -c"
},
"repository": {
"type": "git",
"url": "git+https://github.com/overture-stack/lectern.git"
},
"author": "andricDu",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/overture-stack/lectern/issues"
},
"homepage": "https://github.com/overture-stack/lectern#readme",
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/chai": "^4.1.7",
"@types/errorhandler": "0.0.32",
"@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.3.2",
"@types/lodash": "^4.14.136",
"@types/memoizee": "^0.4.7",
"@types/mocha": "^5.2.7",
"@types/ms": "^0.7.31",
"@types/swagger-ui-express": "^3.0.1",
"chai": "^4.3.6",
"chai-http": "^4.3.0",
"concurrently": "^5.3.0",
"gitmoji-cli": "^4.8.0",
"husky": "^3.1.0",
"mocha": "^9.2.0",
"nodemon": "^2.0.15",
"prettier": "^2.8.0",
"testcontainers": "^1.1.19",
"ts-node": "^10.0.0",
"typescript": "^5.0.0"
},
"dependencies": {
"ajv": "^8.0.0",
"axios": "^1.0.0",
"body-parser": "^1.19.2",
"dotenv": "^16.0.0",
"errorhandler": "^1.5.1",
"express": "^4.18.2",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"memoizee": "^0.4.15",
"mongoose": "^7.0.0",
"ms": "^2.1.3",
"node-vault": "^0.9.22",
"swagger-ui-express": "^4.3.0",
"winston": "^3.6.0"
},
"prettier": {
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"arrowParens": "always",
"useTabs": true
}
"name": "lectern",
"version": "2.0.0-next0",
joneubank marked this conversation as resolved.
Show resolved Hide resolved
"description": "Data Dictionary Management",
"scripts": {
"start": "npm run serve",
"build": "npm run build-ts",
"serve": "node dist/server.js",
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these steps can be avoided altogether using ts-node.
it's as close as it gets right now to treating ts like an actual language 😆

"watch-node": "nodemon dist/server.js",
"test": "mocha ",
"watch-test": "mocha --watch",
"build-ts": "tsc",
"watch-ts": "tsc -w",
"debug": "npm run build && npm run watch-debug",
"serve-debug": "nodemon --inspect dist/server.js",
"watch-debug": "nodemon --watch 'src/**/*.ts' --ignore 'src/**/*.spec.ts' --exec node --inspect -r ts-node/register ./src/server.ts",
"gitmoji-config": "gitmoji -g",
"commit": "gitmoji -c"
},
"repository": {
"type": "git",
"url": "git+https://github.com/overture-stack/lectern.git"
},
"author": "andricDu",
joneubank marked this conversation as resolved.
Show resolved Hide resolved
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/overture-stack/lectern/issues"
},
"homepage": "https://github.com/overture-stack/lectern#readme",
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/chai": "^4.1.7",
"@types/errorhandler": "0.0.32",
"@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.3.2",
"@types/lodash": "^4.14.136",
"@types/memoizee": "^0.4.7",
"@types/mocha": "^5.2.7",
"@types/ms": "^0.7.31",
"@types/swagger-ui-express": "^3.0.1",
"chai": "^4.3.6",
"chai-http": "^4.3.0",
"concurrently": "^5.3.0",
"gitmoji-cli": "^4.8.0",
"husky": "^3.1.0",
"mocha": "^9.2.0",
"nodemon": "^2.0.15",
"prettier": "^2.8.0",
"testcontainers": "^1.1.19",
"ts-node": "^10.0.0",
"typescript": "^5.0.0"
},
"dependencies": {
"ajv": "^8.0.0",
"axios": "^1.0.0",
"body-parser": "^1.19.2",
"dotenv": "^16.0.0",
"errorhandler": "^1.5.1",
"express": "^4.18.2",
"immer": "^10.0.2",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"memoizee": "^0.4.15",
"mongoose": "^7.0.0",
"ms": "^2.1.3",
"node-vault": "^0.9.22",
"swagger-ui-express": "^4.3.0",
"winston": "^3.6.0",
"zod": "^3.21.4"
},
"prettier": {
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"arrowParens": "always",
"useTabs": true
}
}
16 changes: 16 additions & 0 deletions samples/dictionary/simple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Simple",
"version": "1.0",
"schemas": [
{
"name": "primatives",
"description": "Includes one field of each primative type without any restrictions. No Frills.",
joneubank marked this conversation as resolved.
Show resolved Hide resolved
"fields": [
{ "name": "boolean_field", "valueType": "boolean" },
{ "name": "integer_field", "valueType": "integer" },
{ "name": "number_field", "valueType": "number" },
{ "name": "string_field", "valueType": "string" }
]
}
]
}
10 changes: 10 additions & 0 deletions samples/schemas/primatives.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "primatives",
"description": "Includes one field of each primative type without any restrictions. No Frills.",
joneubank marked this conversation as resolved.
Show resolved Hide resolved
"fields": [
{ "name": "boolean_field", "valueType": "boolean" },
{ "name": "integer_field", "valueType": "integer" },
{ "name": "number_field", "valueType": "number" },
{ "name": "string_field", "valueType": "string" }
]
}
2 changes: 1 addition & 1 deletion src/app-health.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2023 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand Down
74 changes: 21 additions & 53 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2023 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand All @@ -17,35 +17,19 @@
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import express, { RequestHandler, Express } from 'express';
import bodyParser from 'body-parser';
import { AppConfig } from './config/appConfig';
import * as dictionaryController from './controllers/dictionaryController';
import { errorHandler } from './utils/errors';
import express, { Express } from 'express';
import * as swaggerUi from 'swagger-ui-express';
import * as swagger from './config/swagger.json';
import ego from './services/egoTokenService';
import logger from './config/logger';
import { dbHealth, Status } from './app-health';

/**
* Decorator to handle errors from async express route handlers
*/
const wrapAsync = (fn: RequestHandler<any, any, any, any>): RequestHandler => {
return (req, res, next) => {
const routePromise: any = fn(req, res, next);
if (routePromise.catch) {
routePromise.catch(next);
}
};
};
import { AppConfig } from './config/appConfig';
import logger from './config/logger';
import * as swagger from './config/swagger.json';
import dictionaryRouter from './routers/dictionaryRouter';
import diffRouter from './routers/diffRouter';
import healthRouter from './routers/healthRouter';
import { errorHandler } from './utils/errors';

const App = (config: AppConfig): Express => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not your code, but seeing how lines 35 and 36 rely on this, those should receive a default/fallback value here.

/**
* Auth Decorator
*/
const egoDecorator = process.env.AUTH_ENABLED === 'true' ? ego() : wrapAsync;

// Create Express server with mongoConfig
const app = express();
const serverPort = config.serverPort();
Expand All @@ -60,43 +44,27 @@ const App = (config: AppConfig): Express => {
}),
);

swagger['info']['version'] = process.env.npm_package_version;
app.use(openApiPath, swaggerUi.serve, swaggerUi.setup(swagger));

logger.info(`OpenAPI setup... done: http://localhost:${serverPort}${openApiPath}`);

app.get('/health', (req, res) => {
if (dbHealth.status == Status.OK) {
const resBody = {
appStatus: 'Up',
dbStatus: dbHealth.status,
};
return res.status(200).send(resBody);
} else {
const resBody = {
appStatus: 'Error/Unknown',
dbStatus: dbHealth.status,
};
return res.status(500).send(resBody);
}
});

// Root Handler:
app.get('/', (_, res) => {
const details = {
app: 'Lectern',
version: process.env.npm_package_version,
commit: process.env.COMMIT_SHA,
};
res.send(details);
});
app.use('/health', healthRouter);
app.use('/dictionaries', dictionaryRouter);
app.use('/diff', diffRouter);

app.get('/dictionaries', wrapAsync(dictionaryController.listDictionaries));
app.post('/dictionaries', egoDecorator(dictionaryController.createDictionary));
app.get('/dictionaries/:dictId', wrapAsync(dictionaryController.getDictionary));
app.post('/dictionaries/:dictId/schemas', egoDecorator(dictionaryController.addSchema));
app.put('/dictionaries/:dictId/schemas', egoDecorator(dictionaryController.updateSchema));
app.get('/diff/', wrapAsync(dictionaryController.diffDictionaries));
/**
* Swagger Setup
*/
swagger.info.version = process.env.npm_package_version || '';

app.use(openApiPath, swaggerUi.serve, swaggerUi.setup(swagger));
logger.info(`Access swagger docs: http://localhost:${serverPort}${openApiPath}`);

// Error handler must be added last to capture all thrown errors.
app.use(errorHandler);

return app;
Expand Down
Loading