-
Notifications
You must be signed in to change notification settings - Fork 0
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
Add API request validation #3
Changes from all commits
027a98a
0b5d864
a66550e
d8a87eb
0f6e4d6
53d1307
21ffc34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules/ | ||
.DS_Store | ||
.env.local |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
build | ||
.build | ||
.serverless | ||
node_modules | ||
.idea | ||
.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,39 @@ | ||
listGames: | ||
handler: src/api/game/crud.list | ||
handler: src/api/game/crud.listHandler | ||
vpc: ${self:custom.vpc} | ||
events: | ||
- httpApi: | ||
method: GET | ||
path: /game | ||
integration: lambda | ||
|
||
createGame: | ||
handler: src/api/game/crud.create | ||
handler: src/api/game/crud.createHandler | ||
vpc: ${self:custom.vpc} | ||
events: | ||
- httpApi: | ||
method: POST | ||
path: /game | ||
integration: lambda | ||
|
||
getGameById: | ||
handler: src/api/game/crud.getById | ||
handler: src/api/game/crud.getByIdHandler | ||
vpc: ${self:custom.vpc} | ||
events: | ||
- httpApi: | ||
method: GET | ||
path: /game/{gameId} | ||
integration: lambda | ||
|
||
updateGameById: | ||
handler: src/api/game/crud.updateById | ||
handler: src/api/game/crud.updateByIdHandler | ||
vpc: ${self:custom.vpc} | ||
events: | ||
- httpApi: | ||
method: PATCH | ||
path: /game/{gameId} | ||
integration: lambda | ||
|
||
deleteGameById: | ||
handler: src/api/game/crud.delete | ||
handler: src/api/game/crud.deleteByIdHandler | ||
vpc: ${self:custom.vpc} | ||
events: | ||
- httpApi: | ||
method: DELETE | ||
path: /game/{gameId} | ||
integration: lambda |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
type PaginatedResultPromise<T> = Promise<import("aws-lambda").APIGatewayProxyResultV2<import("<%= title %>-core").PaginatedResponse<T>>> | ||
type ResultPromise<T> = Promise<import("aws-lambda").APIGatewayProxyResultV2<T>> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,8 @@ | ||
import { db } from "../../db" | ||
import { Connection } from 'typeorm'; | ||
import { Game, PaginatedResponse, gameFactory } from '<%= title %>-core' | ||
import { APIGatewayProxyEventV2 } from 'aws-lambda' | ||
import { list } from './crud' | ||
import { getById } from './crud' | ||
import { create } from './crud' | ||
import { updateById } from './crud' | ||
import { deleteById } from './crud' | ||
import { Game, PaginatedResponse, gameFactory, GameSchemaLite } from '<%= title %>-core' | ||
import { APIGatewayProxyEventV2, APIGatewayProxyEventPathParameters } from 'aws-lambda' | ||
import { listHandler, getByIdHandler, createHandler, updateByIdHandler, deleteByIdHandler } from './crud' | ||
|
||
|
||
let conn: Connection | ||
|
@@ -34,24 +30,29 @@ describe('Test entity API', () => { | |
|
||
await repo.save(entity) | ||
|
||
const entities: Game[] = (await list({} as APIGatewayProxyEventV2) as PaginatedResponse<Game>).items | ||
const entities: Game[] = (await listHandler({} as APIGatewayProxyEventV2) as PaginatedResponse<Game>).items | ||
|
||
const entityIds: string[] = entities.map(e => e.id) | ||
expect(entityIds).toContain(entity.id) | ||
} | ||
) | ||
|
||
it( | ||
"Test creating an entity.", | ||
"Test creating an entity. Also test that the validator for request body works.", | ||
async () => { | ||
const entity: Game = await create({ body: JSON.stringify(gameFactory.build()) } as unknown as APIGatewayProxyEventV2) as Game | ||
await createHandler({ body: JSON.stringify({ name: 1234 }) } as unknown as APIGatewayProxyEventV2) as Game | ||
|
||
const repo = conn.getRepository(Game) | ||
|
||
const count: number = await repo.createQueryBuilder("game").getCount() | ||
let count: number = await repo.createQueryBuilder("game").getCount() | ||
|
||
expect(count).toEqual(1) | ||
expect(count).toEqual(0) | ||
|
||
await createHandler({ body: JSON.stringify(gameFactory.build()) } as unknown as APIGatewayProxyEventV2) as Game | ||
|
||
count = await repo.createQueryBuilder("game").getCount() | ||
|
||
expect(count).toEqual(1) | ||
} | ||
) | ||
|
||
|
@@ -67,7 +68,7 @@ describe('Test entity API', () => { | |
// Save doesn't return id when `create` with relationships is used :( | ||
entityToGet = await repo.createQueryBuilder("game").getOneOrFail() | ||
|
||
const receivedEntity: Game = await getById({ pathParameters: { gameId: entityToGet.id } } as unknown as APIGatewayProxyEventV2) as Game | ||
const receivedEntity: Game = await getByIdHandler({ pathParameters: { gameId: entityToGet.id } } as unknown as APIGatewayProxyEventV2) as Game | ||
|
||
expect(receivedEntity.id).toEqual(entityToGet.id) | ||
} | ||
|
@@ -89,7 +90,7 @@ describe('Test entity API', () => { | |
entityToUpdate = await repo.createQueryBuilder("game").getOneOrFail() | ||
|
||
expect(entityToUpdate.name).not.toEqual(updatedEntity.name) | ||
await updateById({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game | ||
await updateByIdHandler({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type casting with
When testing we don't need and don't have things featured on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can |
||
|
||
expect((await repo.findOneOrFail(entityToUpdate.id)).name).toEqual(updatedEntity.name) | ||
} | ||
|
@@ -109,7 +110,7 @@ describe('Test entity API', () => { | |
|
||
expect(await repo.createQueryBuilder("game").getCount()).toEqual(1) | ||
|
||
await deleteById({ pathParameters: { gameId: entityToDelete.id } } as unknown as APIGatewayProxyEventV2) as Game | ||
await deleteByIdHandler({ pathParameters: { gameId: entityToDelete.id } } as unknown as APIGatewayProxyEventV2) as Game | ||
|
||
expect(await repo.createQueryBuilder("game").getCount()).toEqual(0) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,114 +1,45 @@ | ||
import { Game, PaginatedResponse } from "<%= title %>-core" | ||
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda" | ||
import { db } from "../../db" | ||
import { getPagesData, getPaginationData } from "../../util/pagination" | ||
import { Game } from "<%= title %>-core" | ||
import { GameSchemaLite } from "<%= title %>-core" | ||
import { getPageParams } from "../../util/pagination" | ||
import createHttpError from "http-errors" | ||
import { applyErrorHandlingAndValidation } from "../../util/serialization" | ||
import { APIGatewayProxyEventV2, APIGatewayProxyEventPathParameters } from "aws-lambda" | ||
import { listGames, createGame, getGameById, updateGameById, deleteGameById } from "../../domain/game" | ||
|
||
|
||
interface ICreateGameRequest { | ||
name: string | ||
} | ||
const create = async (event: { body: GameSchemaLite }): ResultPromise<Game> => | ||
await createGame(event.body) | ||
|
||
interface IUpdateGameRequest { | ||
name: string | ||
const list = async (event: APIGatewayProxyEventV2): PaginatedResultPromise<Game> => { | ||
const pageParams = getPageParams(event.queryStringParameters) | ||
return await listGames(pageParams) | ||
} | ||
|
||
const getById = async (event: { pathParameters: APIGatewayProxyEventPathParameters }): ResultPromise<Game> => { | ||
if (!event.pathParameters || !event.pathParameters["gameId"]) | ||
throw createHttpError(404, "No path parameters found or gameId not present in them") | ||
|
||
export async function create(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<Game>> { | ||
if (!event.body) { | ||
console.warn("No request body provided") | ||
return { | ||
statusCode: 400, | ||
} | ||
} | ||
const body: ICreateGameRequest = JSON.parse(event.body) | ||
|
||
const name = body.name | ||
|
||
const conn = await db.getConnection() | ||
|
||
const repo = conn.getRepository(Game) | ||
|
||
const game = repo.create({ | ||
name: name, | ||
}) | ||
|
||
await repo.save(game) | ||
|
||
|
||
return game | ||
return await getGameById(event.pathParameters["gameId"]) | ||
} | ||
|
||
export async function list(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<PaginatedResponse<Game>>> { | ||
/** | ||
* Get a paginated list | ||
*/ | ||
const pagesData = getPagesData(event.queryStringParameters) | ||
const updateById = async (event: { body: GameSchemaLite, pathParameters: APIGatewayProxyEventPathParameters }): ResultPromise<Game> => { | ||
if (!event.pathParameters || !event.pathParameters["gameId"]) | ||
throw createHttpError(404, "No path parameters found or gameId not present in them") | ||
|
||
const conn = await db.getConnection() | ||
const games = await conn.getRepository(Game).createQueryBuilder("game").getMany() | ||
|
||
const totalCount = await conn.getRepository(Game).createQueryBuilder("game").getCount() | ||
|
||
return { | ||
items: games, | ||
paginationData: getPaginationData(totalCount, pagesData), | ||
} | ||
const gameId = event.pathParameters["gameId"] | ||
return await updateGameById(gameId, event.body) | ||
} | ||
|
||
export async function getById(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<Game>> { | ||
if (!event.pathParameters || !event.pathParameters["gameId"]) { | ||
console.warn("No path parameters found or gameId not present in them") | ||
return { statusCode: 400 } | ||
} | ||
|
||
const gameId: string = event.pathParameters["gameId"] | ||
|
||
const conn = await db.getConnection() | ||
const deleteById = async (event: { pathParameters: APIGatewayProxyEventPathParameters }): ResultPromise<void> => { | ||
if (!event.pathParameters || !event.pathParameters["gameId"]) | ||
throw createHttpError(404, "No path parameters found or gameId not present in them") | ||
|
||
const repo = conn.getRepository(Game) | ||
|
||
const game = await repo.findOneOrFail(gameId) | ||
|
||
return game | ||
const gameId = event.pathParameters["gameId"] | ||
await deleteGameById(gameId) | ||
} | ||
|
||
|
||
export async function updateById(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<Game>> { | ||
if (!event.pathParameters || !event.pathParameters["gameId"] || !event.body) { | ||
console.warn("No path parameters found or gameId not present in them") | ||
return { statusCode: 400 } | ||
} | ||
|
||
const gameId: string = event.pathParameters["gameId"] | ||
|
||
const body: IUpdateGameRequest = JSON.parse(event.body) | ||
|
||
|
||
const conn = await db.getConnection() | ||
|
||
const repo = conn.getRepository(Game) | ||
|
||
const game = await repo.findOneOrFail(gameId) | ||
|
||
return await repo.save({ | ||
...game, | ||
...body | ||
}) | ||
} | ||
|
||
export async function deleteById(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<void>> { | ||
if (!event.pathParameters || !event.pathParameters["gameId"]) { | ||
console.warn("No path parameters found or gameId not present in them") | ||
return { statusCode: 400 } | ||
} | ||
|
||
const gameId: string = event.pathParameters["gameId"] | ||
|
||
const conn = await db.getConnection() | ||
|
||
const repo = conn.getRepository(Game) | ||
|
||
const game = await repo.findOneOrFail(gameId) | ||
|
||
await repo.remove(game) | ||
} | ||
export const createHandler = applyErrorHandlingAndValidation<Game>(GameSchemaLite, create) | ||
export const listHandler = applyErrorHandlingAndValidation<Game[]>(Game, list) | ||
export const getByIdHandler = applyErrorHandlingAndValidation<Game>(Game, getById) | ||
export const updateByIdHandler = applyErrorHandlingAndValidation<Game>(GameSchemaLite, updateById) | ||
export const deleteByIdHandler = applyErrorHandlingAndValidation<Game>(Game, deleteById) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When trying to use absolute paths. Deployed code was failing with
unable to find module
error.Hence backend uses relative paths now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it's this issue serverless/serverless-plugin-typescript#180