Skip to content

Commit

Permalink
Reorganising schemas (#382)
Browse files Browse the repository at this point in the history
* referencing schemas

* updated search schema

* articles updated

* collections

* refactored schemas used in public services, added validation

* deleted unused files

* updated collectable items

* removed unused schemas

* added still used schema

* generated types
  • Loading branch information
theorm authored Apr 25, 2024
1 parent f15eb90 commit b526d19
Show file tree
Hide file tree
Showing 62 changed files with 1,745 additions and 492 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ $ feathers generate model # Generate a new Model
$ feathers help # Show all commands
```

## Generating Typescipt types from JSON schemas

When a schema is updated, the typescript types should be regenerated. This can be done by running the following command:

```
npm run generate-types
```

## Help

For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com).
Expand Down
94 changes: 88 additions & 6 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"node-fetch": "2.7.0",
"node-http-proxy-json": "^0.1.6",
"passport-github": "^1.1.0",
"express-openapi-validator": "5.1.6",
"pg": "^8.11.3",
"pg-hstore": "^2.3.2",
"redis": "4.6.13",
Expand Down
17 changes: 8 additions & 9 deletions scripts/generate-types.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
const { execSync } = require('node:child_process')
const fs = require('node:fs')

const basePath = './src/services'
const basePath = './src/schema'

// for now support only the followin services until we migrate everything:
const supportedServices = ['text-reuse-clusters']
const schemaBits = ['schemas', 'parameters', 'requestBodies', 'responses']

const directories = fs
.readdirSync('./src/services')
.readdirSync(basePath)
.filter(item => {
return fs.statSync(`${basePath}/${item}`).isDirectory()
})
.filter(item => supportedServices.includes(item))
.filter(item => schemaBits.includes(item))

directories.forEach(service => {
directories.forEach(dir => {
// eslint-disable-next-line no-console
console.log(`Generating types for service ${service}...`)
console.log(`Generating types for service ${dir}...`)
const command =
`quicktype --src-lang schema --src ${basePath}/${service}/schema/**/*.json` +
` --out ${basePath}/${service}/models/generated.ts --lang ts --just-types`
`quicktype --src-lang schema --src ${basePath}/${dir}/*.json` +
` --out ./src/models/generated/${dir}.ts --lang ts --just-types`
execSync(command)
})
4 changes: 4 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import sequelize from './sequelize'
import solr from './solr'
import media from './services/media'
import proxy from './services/proxy'
import schemas from './services/schemas'
import { ensureServiceIsFeathersCompatible } from './util/feathers'
import openApiValidator from './middleware/openApiValidator'

const path = require('path')
const compress = require('compression')
Expand Down Expand Up @@ -87,5 +89,7 @@ app.configure(appHooks)
// configure express services
app.configure(media)
app.configure(proxy)
app.configure(schemas)
app.configure(openApiValidator)

module.exports = app
4 changes: 3 additions & 1 deletion src/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ class HashedPasswordVerifier extends LocalStrategy {
}

module.exports = app => {
const isPublicApi = app.get('isPublicApi')
const authentication = new CustomisedAuthenticationService(app)

authentication.register('jwt', new JWTStrategy())
authentication.register('local', new HashedPasswordVerifier())

app.use('/authentication', authentication, {
methods: ['create', 'remove'],
methods: isPublicApi ? ['create'] : undefined,
events: [],
docs: createSwaggerServiceOptions({ schemas: {}, docs }),
})
}
38 changes: 38 additions & 0 deletions src/middleware/openApiValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types'
import RefParser, { FileInfo } from '@apidevtools/json-schema-ref-parser'
import type { ImpressoApplication } from '../types'
import type { Application } from '@feathersjs/express'
import * as OpenApiValidator from 'express-openapi-validator'
import fs from 'fs'

export default async (app: ImpressoApplication & Application) => {
const isPublicApi = app.get('isPublicApi')
if (!isPublicApi) return

if (!('docs' in app)) throw new Error('`docs` property not found in app object. Is swagger initialized?')
const spec = (app as any)['docs'] as unknown as OpenAPIV3.Document

const dereferencedOpenApiSpec = await RefParser.dereference(spec, {
resolve: {
file: {
/**
* All JSON schema files are relative to the
* `src` / `dist` directory. Adding it here.
*/
read: (file: FileInfo) => {
const cwd = process.cwd()
const filePath = file.url.replace(cwd, `${cwd}/dist`)
return fs.readFileSync(filePath, 'utf-8')
},
},
},
})

const middlewares = OpenApiValidator.middleware({
apiSpec: dereferencedOpenApiSpec as unknown as OpenAPIV3.Document,
validateRequests: true, // (default)
validateResponses: true, // false by default
validateApiSpec: false,
})
middlewares.forEach(middleware => app.use(middleware))
}
34 changes: 32 additions & 2 deletions src/middleware/swagger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
import swagger, { swaggerUI } from 'feathers-swagger'
import { logger } from '../logger'
import { ImpressoApplication } from '../types'
import fs from 'fs'
import path from 'path'
import { Application } from '@feathersjs/express'

const schemaBaseDir = path.join(__dirname, '../schema')

interface SchemaRef {
$ref: string
}

const getFilesAsSchemaRefs = (dir: string, prefix: string): Record<string, SchemaRef> => {
const allFiles = fs.readdirSync(dir)

return allFiles
.filter(f => f.endsWith('.json'))
.reduce(
(acc, f) => {
const key = path.basename(f, '.json')
acc[key] = {
$ref: `${prefix}/${key}.json`,
}
return acc
},
{} as Record<string, SchemaRef>
)
}

function getRedirectPrefix({ req, ctx }: any) {
const headers = (req && req.headers) || (ctx && ctx.headers) || {}
Expand Down Expand Up @@ -33,7 +59,7 @@ function generateSwaggerUIInitializerScript({ docsJsonPath, ctx, req }: any) {
`
}

export default (app: ImpressoApplication) => {
export default (app: ImpressoApplication & Application) => {
if (!app.get('isPublicApi')) {
logger.info('Internal API - swagger middleware is disabled')
return
Expand All @@ -51,6 +77,10 @@ export default (app: ImpressoApplication) => {
version: require('../../package.json').version,
},
components: {
schemas: getFilesAsSchemaRefs(`${schemaBaseDir}/schemas`, './schema/schemas'),
requestBodies: getFilesAsSchemaRefs(`${schemaBaseDir}/requestBodies`, './schema/requestBodies'),
responses: getFilesAsSchemaRefs(`${schemaBaseDir}/responses`, './schema/responses'),
parameters: getFilesAsSchemaRefs(`${schemaBaseDir}/parameters`, './schema/parameters'),
securitySchemes: {
BearerAuth: {
type: 'http',
Expand All @@ -73,5 +103,5 @@ export default (app: ImpressoApplication) => {
getSwaggerInitializerScript: generateSwaggerUIInitializerScript,
}),
})
return app.configure(swaggerItem)
app.configure(swaggerItem)
}
6 changes: 6 additions & 0 deletions src/models/generated/parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Add-ons for text reuse passages find method.
*/
export interface Parameters {
newspaper?: any;
}
15 changes: 15 additions & 0 deletions src/models/generated/requestBodies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface AuthenticationCreateRequest {
email: string;
password: string;
strategy: Strategy;
}

export enum Strategy {
Local = "local",
}

export interface NewCollection {
description?: string;
name: string;
status?: string;
}
Loading

0 comments on commit b526d19

Please sign in to comment.