Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exports.up = function (knex) {

table.timestamp('createdAt').defaultTo(knex.fn.now())
table.timestamp('updatedAt').defaultTo(knex.fn.now())
table.timestamp('deletedAt')
table.timestamp('deletedAt').nullable()
})
}

Expand Down
3 changes: 2 additions & 1 deletion packages/point-of-sale/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"jest-openapi": "^0.14.2",
"testcontainers": "^10.16.0",
"tmp": "^0.2.3",
"@types/tmp": "^0.2.6"
"@types/tmp": "^0.2.6",
"node-mocks-http": "^1.16.2"
}
}
18 changes: 18 additions & 0 deletions packages/point-of-sale/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import Koa, { DefaultState } from 'koa'
import Router from '@koa/router'
import bodyParser from 'koa-bodyparser'
import cors from '@koa/cors'
import { CreateMerchantContext, MerchantRoutes } from './merchant/routes'

export interface AppServices {
logger: Promise<Logger>
knex: Promise<Knex>
config: Promise<IAppConfig>
merchantRoutes: Promise<MerchantRoutes>
}

export type AppContainer = IocContract<AppServices>
Expand All @@ -25,6 +27,13 @@ export interface AppContextData {

export type AppContext = Koa.ParameterizedContext<DefaultState, AppContextData>

export type AppRequest<ParamsT extends string = string> = Omit<
AppContext['request'],
'params'
> & {
params: Record<ParamsT, string>
}

export class App {
private posServer!: Server
public isShuttingDown = false
Expand All @@ -47,6 +56,15 @@ export class App {
ctx.status = 200
})

const merchantRoutes = await this.container.use('merchantRoutes')

// POST /merchants
// Create merchant
router.post<DefaultState, CreateMerchantContext>(
'/merchants',
merchantRoutes.create
)

koa.use(cors())
koa.use(router.routes())

Expand Down
8 changes: 8 additions & 0 deletions packages/point-of-sale/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Config } from './config/app'
import { App, AppServices } from './app'
import createLogger from 'pino'
import { createMerchantService } from './merchant/service'
import { createMerchantRoutes } from './merchant/routes'

export function initIocContainer(
config: typeof Config
Expand Down Expand Up @@ -65,6 +66,13 @@ export function initIocContainer(
return createMerchantService({ logger, knex })
})

container.singleton('merchantRoutes', async (deps) => {
return createMerchantRoutes({
logger: await deps.use('logger'),
merchantService: await deps.use('merchantService')
})
})

return container
}

Expand Down
15 changes: 15 additions & 0 deletions packages/point-of-sale/src/merchant/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class POSMerchantRouteError extends Error {
public status: number
public details?: Record<string, unknown>

constructor(
status: number,
message: string,
details?: Record<string, unknown>
) {
super(message)
this.name = 'POSMerchantRouteError'
this.status = status
this.details = details
}
}
2 changes: 1 addition & 1 deletion packages/point-of-sale/src/merchant/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export class Merchant extends BaseModel {
})

public name!: string
public deletedAt!: Date | null
public deletedAt?: Date | null
}
67 changes: 67 additions & 0 deletions packages/point-of-sale/src/merchant/routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { IocContract } from '@adonisjs/fold'
import { createContext } from '../tests/context'
import { createTestApp, TestContainer } from '../tests/app'
import { Config } from '../config/app'
import { initIocContainer } from '..'
import { AppServices } from '../app'
import { truncateTables } from '../tests/tableManager'
import {
CreateMerchantContext,
MerchantRoutes,
createMerchantRoutes
} from './routes'
import { MerchantService } from './service'

describe('Merchant Routes', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let merchantRoutes: MerchantRoutes
let merchantService: MerchantService

beforeAll(async (): Promise<void> => {
deps = initIocContainer(Config)
appContainer = await createTestApp(deps)
merchantService = await deps.use('merchantService')
const logger = await deps.use('logger')

merchantRoutes = createMerchantRoutes({
merchantService,
logger
})
})

afterEach(async (): Promise<void> => {
await truncateTables(deps)
})

afterAll(async (): Promise<void> => {
await appContainer.shutdown()
})

describe('create', (): void => {
test('Creates a merchant', async (): Promise<void> => {
const merchantData = {
name: 'Test Merchant'
}

const ctx = createContext<CreateMerchantContext>(
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
},
{}
)
ctx.request.body = merchantData

await merchantRoutes.create(ctx)

expect(ctx.status).toBe(200)
expect(ctx.response.body).toEqual({
id: expect.any(String),
name: merchantData.name
})
})
})
})
54 changes: 54 additions & 0 deletions packages/point-of-sale/src/merchant/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { AppContext } from '../app'
import { BaseService } from '../shared/baseService'
import { MerchantService } from './service'
import { POSMerchantRouteError } from './errors'

interface ServiceDependencies extends BaseService {
merchantService: MerchantService
}

type CreateMerchantRequest = Exclude<AppContext['request'], 'body'> & {
body: {
name: string
}
}

export type CreateMerchantContext = Exclude<AppContext, 'request'> & {
request: CreateMerchantRequest
}

export interface MerchantRoutes {
create(ctx: CreateMerchantContext): Promise<void>
}

export function createMerchantRoutes(
deps_: ServiceDependencies
): MerchantRoutes {
const log = deps_.logger.child({
service: 'MerchantRoutes'
})

const deps = {
...deps_,
logger: log
}

return {
create: (ctx: CreateMerchantContext) => createMerchant(deps, ctx)
}
}

async function createMerchant(
deps: ServiceDependencies,
ctx: CreateMerchantContext
): Promise<void> {
const { body } = ctx.request
try {
const merchant = await deps.merchantService.create(body.name)

ctx.status = 200
ctx.body = { id: merchant.id, name: merchant.name }
} catch (err) {
throw new POSMerchantRouteError(400, 'Could not create merchant', { err })
}
}
8 changes: 7 additions & 1 deletion packages/point-of-sale/src/merchant/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ describe('Merchant Service', (): void => {
describe('create', (): void => {
test('creates a merchant', async (): Promise<void> => {
const merchant = await merchantService.create('Test merchant')
expect(merchant).toEqual({ id: merchant.id, name: 'Test merchant' })
expect(merchant).toMatchObject({
name: 'Test merchant',
createdAt: expect.any(Date),
updatedAt: expect.any(Date)
})
expect(typeof merchant.id).toBe('string')
expect(merchant.deletedAt).toBeUndefined()
})
})

Expand Down
2 changes: 2 additions & 0 deletions packages/point-of-sale/src/shared/baseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export abstract class BaseModel extends PaginationModel {
public $beforeInsert(context: QueryContext): void {
super.$beforeInsert(context)
this.id = this.id || uuid()
this.createdAt = new Date()
this.updatedAt = new Date()
}

public $beforeUpdate(_opts: ModelOptions, _queryContext: QueryContext): void {
Expand Down
24 changes: 24 additions & 0 deletions packages/point-of-sale/src/tests/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IocContract } from '@adonisjs/fold'
import * as httpMocks from 'node-mocks-http'
import Koa from 'koa'
import { AppContext, AppContextData, AppRequest } from '../app'

export function createContext<T extends AppContext>(
reqOpts: httpMocks.RequestOptions,
params: Record<string, string> = {},
container?: IocContract
): T {
const req = httpMocks.createRequest(reqOpts)
const res = httpMocks.createResponse({ req })
const koa = new Koa<unknown, AppContextData>()
const ctx = koa.createContext(req, res)
ctx.params = (ctx.request as AppRequest).params = params
if (reqOpts.query) {
ctx.request.query = reqOpts.query
}
if (reqOpts.body !== undefined) {
ctx.request.body = reqOpts.body
}
ctx.container = container
return ctx as T
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

Loading