Skip to content

Commit

Permalink
feat(docs): setup extension of documentation on route instance
Browse files Browse the repository at this point in the history
Allow extending documentation on route instances, generate Input type objects, add parameters to
CRUD endpoints'

BREAKING CHANGE: Name is no longer automatically added to User resource on auth package
  • Loading branch information
Frantz Kati committed Nov 24, 2020
1 parent 620c0bb commit 130b971
Show file tree
Hide file tree
Showing 14 changed files with 542 additions and 171 deletions.
1 change: 1 addition & 0 deletions examples/blog/resources/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const User = resource('User')
.hideOnUpdate()
.hideOnIndex()
.hideOnDetail()
.hidden()
.rules('required', 'min:8', 'max:24')
.notNullable(),
hasMany('Post'),
Expand Down
1 change: 1 addition & 0 deletions packages/auth/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Mail } from '@tensei/mail'
import { UserEntity } from './src/config'
import { AnyEntity } from '@mikro-orm/core'
import * as Formatter from 'express-response-formatter'

declare global {
namespace Express {
export interface Request {
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface AuthPluginConfig {
secretKey: string
refreshTokenExpiresIn: number
}
refresTokenCookieName: string
refreshTokenCookieName: string
teams: boolean
cookieOptions: Omit<CookieOptions, 'httpOnly' | 'maxAge'>
verifyEmails?: boolean
Expand Down
180 changes: 156 additions & 24 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { graphQlQuery } from '@tensei/common'
import { ApiContext } from '@tensei/common'
import { GraphQlQueryContract } from '@tensei/common'
import { route } from '@tensei/common'
import { snakeCase } from 'change-case'

type ResourceShortNames =
| 'user'
Expand Down Expand Up @@ -80,7 +81,7 @@ class Auth {
refreshTokenExpiresIn: 60 * 60 * 24 * 7
},
cookieOptions: {},
refresTokenCookieName: '___refresh__token',
refreshTokenCookieName: '___refresh__token',
teams: false,
teamFields: [],
twoFactorAuth: false,
Expand Down Expand Up @@ -235,7 +236,6 @@ class Auth {

const userResource = resource(this.config.userResource)
.fields([
text('Name').searchable().nullable(),
text('Email')
.unique()
.searchable()
Expand Down Expand Up @@ -388,13 +388,14 @@ class Auth {
belongsTo(this.config.userResource).nullable(),
textarea('Access Token')
.hidden()
.hideFromApi()
.hideOnUpdate()
.hideOnIndex()
.hideOnDetail()
.hideOnCreate(),
text('Email'),
textarea('Temporal Token').nullable(),
json('Payload'),
textarea('Temporal Token').nullable().hidden(),
json('Payload').hidden().hideFromApi(),
text('Provider').rules('required'),
text('Provider User ID')
])
Expand Down Expand Up @@ -740,6 +741,14 @@ class Auth {
)
}

private getRegistrationFieldsDocs() {
const properties: any = {}

// TODO: Calculate and push new registration fields to be exposed to API

return properties
}

private extendRoutes() {
const name = this.resources.user.data.slugSingular

Expand All @@ -754,33 +763,120 @@ class Auth {
.extend({
docs: {
...extend,
summary: `Login an existing ${name}.`
summary: `Login an existing ${name}.`,
parameters: [
{
required: true,
type: 'object',
name: 'body',
in: 'body',
schema: {
$ref: `#/definitions/LoginInput`
}
}
],
definitions: {
LoginInput: {
type: 'object',
properties: {
email: {
required: true,
type: 'string',
format: 'email'
},
password: {
required: true,
type: 'string'
}
}
}
}
}
})
.handle(async (request, response) =>
response.formatter.ok(await this.login(request as any))
.handle(async (request, { formatter: { ok, unprocess } }) =>
{
try {
return ok(await this.login(request as any))
} catch (error) {
return unprocess(error)
}
}
),
route(`Register ${name}`)
.path(this.getApiPath('register'))
.post()
.extend({
docs: {
...extend,
summary: `Register a new ${name}.`
summary: `Register a new ${name}.`,
parameters: [
{
required: true,
type: 'object',
name: 'body',
in: 'body',
schema: {
$ref: `#/definitions/RegisterInput`
}
}
],
definitions: {
RegisterInput: {
type: 'object',
properties: {
email: {
required: true,
type: 'string',
format: 'email'
},
password: {
required: true,
type: 'string'
},
...this.getRegistrationFieldsDocs()
}
}
}
}
})
.handle(async (request, response) =>
response.formatter.created(
await this.register(request as any)
)
.handle(
async (request, { formatter: { created, unprocess } }) => {
try {
return created(await this.register(request as any))
} catch (error) {
return unprocess(error)
}
}
),
route(`Request password reset`)
.path(this.getApiPath('passwords/email'))
.post()
.extend({
docs: {
...extend,
summary: `Request a password reset for a ${name} using the ${name} email.`
summary: `Request a password reset for a ${name} using the ${name} email.`,
parameters: [
{
required: true,
type: 'object',
name: 'body',
in: 'body',
schema: {
$ref: `#/definitions/RequestPasswordInput`
}
}
],
definitions: {
RequestPasswordInput: {
properties: {
email: {
type: 'string',
required: true,
format: 'email'
}
}
}
}
}
})
.handle(async (request, response) =>
Expand All @@ -795,7 +891,33 @@ class Auth {
.extend({
docs: {
...extend,
summary: `Reset a ${name} password using a password reset token.`
summary: `Reset a ${name} password using a password reset token.`,
parameters: [
{
required: true,
type: 'object',
name: 'body',
in: 'body',
schema: {
$ref: `#/definitions/ResetPasswordInput`
}
}
],
definitions: {
ResetPasswordInput: {
properties: {
password: {
type: 'string',
required: true
},
token: {
type: 'string',
required: true,
description: `This token was sent to the ${name}'s email. Provide it here to reset the ${name}'s password.`
}
}
}
}
}
})
.handle(async (request, response) =>
Expand Down Expand Up @@ -912,8 +1034,15 @@ class Auth {
.extend({
docs: {
...extend,
summary: `Request a new JWT (access token) using a refresh token.`,
description: `The refresh token is set in cookies response for all endpoints that return an access token (login, register).`
summary: `Request a new (access token) using a refresh token.`,
description: `The refresh token is set in cookies response for all endpoints that return an access token (login, register).`,
parameters: [
{
name: this.config.refreshTokenCookieName,
required: true,
in: 'cookie'
}
]
}
})
.handle(
Expand All @@ -937,7 +1066,14 @@ class Auth {
docs: {
...extend,
summary: `Invalidate a refresh token.`,
description: `Sets the refresh token cookie to an invalid value and expires it.`
description: `Sets the refresh token cookie to an invalid value and expires it.`,
parameters: [
{
name: this.config.refreshTokenCookieName,
required: true,
in: 'cookie'
}
]
}
})
.handle(async (request, response) =>
Expand All @@ -956,7 +1092,7 @@ class Auth {

private setRefreshToken(ctx: ApiContext) {
ctx.res.cookie(
this.config.refresTokenCookieName,
this.config.refreshTokenCookieName,
this.generateJwt(
{
id: ctx.user.id,
Expand Down Expand Up @@ -1063,7 +1199,7 @@ class Auth {
}

private async removeRefreshTokens(ctx: ApiContext) {
ctx.res.cookie(this.config.refresTokenCookieName, '', {
ctx.res.cookie(this.config.refreshTokenCookieName, '', {
...this.config.cookieOptions,
httpOnly: true,
maxAge: 0
Expand All @@ -1073,7 +1209,7 @@ class Auth {
}

private async handleRefreshTokens(ctx: ApiContext) {
const refreshToken = ctx.req.cookies[this.config.refresTokenCookieName]
const refreshToken = ctx.req.cookies[this.config.refreshTokenCookieName]

if (!refreshToken) {
throw ctx.authenticationError('Invalid refresh token.')
Expand Down Expand Up @@ -1839,10 +1975,6 @@ class Auth {
password: 'required|min:8'
}

if (registration) {
rules.name = 'required'
}

return await validateAll(data, rules, {
'email.required': 'The email is required.',
'password.required': 'The password is required.',
Expand Down
55 changes: 55 additions & 0 deletions packages/common/src/fields/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export class Field implements FieldContract {
showOnCreation: true
}

public showHideFieldFromApi = {
hideFromShowApi: false,
hideFromCreateApi: false,
hideFromUpdateApi: false,
hideFromDeleteApi: false,
hideFromFetchApi: false
}

public property: FieldProperty = {
name: '',
type: 'string',
Expand Down Expand Up @@ -420,6 +428,46 @@ export class Field implements FieldContract {
return this
}

public hideFromApi() {
this.showHideFieldFromApi.hideFromCreateApi = true
this.showHideFieldFromApi.hideFromFetchApi = true
this.showHideFieldFromApi.hideFromShowApi = true
this.showHideFieldFromApi.hideFromDeleteApi = true
this.showHideFieldFromApi.hideFromUpdateApi = true

return this
}

public hideFromCreateApi() {
this.showHideFieldFromApi.hideFromCreateApi = true

return this
}

public hideFromUpdateApi() {
this.showHideFieldFromApi.hideFromUpdateApi = true

return this
}

public hideFromDeleteApi() {
this.showHideFieldFromApi.hideFromDeleteApi = true

return this
}

public hideFromFetchApi() {
this.showHideFieldFromApi.hideFromFetchApi = true

return this
}

public hideFromShowApi() {
this.showHideFieldFromApi.hideFromShowApi = true

return this
}

/**
*
* Make this field sortable
Expand Down Expand Up @@ -664,6 +712,13 @@ export class Field implements FieldContract {
return this
}

public hiddenFromApi() {
return (
Object.values(this.showHideFieldFromApi).filter(shown => shown)
.length === 0 || !!this.property.hidden
)
}

/**
*
* Serializes the field for data to be sent
Expand Down
Loading

0 comments on commit 130b971

Please sign in to comment.