Skip to content

Commit a61b299

Browse files
feat(pos): create merchant route (#3538)
* post merchants route --------- Co-authored-by: Nathan Lie <[email protected]>
1 parent 2d4fe93 commit a61b299

File tree

12 files changed

+202
-4
lines changed

12 files changed

+202
-4
lines changed

packages/point-of-sale/migrations/20250708093546_create_merchants_table.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ exports.up = function (knex) {
1010

1111
table.timestamp('createdAt').defaultTo(knex.fn.now())
1212
table.timestamp('updatedAt').defaultTo(knex.fn.now())
13-
table.timestamp('deletedAt')
13+
table.timestamp('deletedAt').nullable()
1414
})
1515
}
1616

packages/point-of-sale/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"jest-openapi": "^0.14.2",
4646
"testcontainers": "^10.16.0",
4747
"tmp": "^0.2.3",
48-
"@types/tmp": "^0.2.6"
48+
"@types/tmp": "^0.2.6",
49+
"node-mocks-http": "^1.16.2"
4950
}
5051
}

packages/point-of-sale/src/app.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import Koa, { DefaultState } from 'koa'
77
import Router from '@koa/router'
88
import bodyParser from 'koa-bodyparser'
99
import cors from '@koa/cors'
10+
import { CreateMerchantContext, MerchantRoutes } from './merchant/routes'
1011

1112
export interface AppServices {
1213
logger: Promise<Logger>
1314
knex: Promise<Knex>
1415
config: Promise<IAppConfig>
16+
merchantRoutes: Promise<MerchantRoutes>
1517
}
1618

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

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

30+
export type AppRequest<ParamsT extends string = string> = Omit<
31+
AppContext['request'],
32+
'params'
33+
> & {
34+
params: Record<ParamsT, string>
35+
}
36+
2837
export class App {
2938
private posServer!: Server
3039
public isShuttingDown = false
@@ -47,6 +56,15 @@ export class App {
4756
ctx.status = 200
4857
})
4958

59+
const merchantRoutes = await this.container.use('merchantRoutes')
60+
61+
// POST /merchants
62+
// Create merchant
63+
router.post<DefaultState, CreateMerchantContext>(
64+
'/merchants',
65+
merchantRoutes.create
66+
)
67+
5068
koa.use(cors())
5169
koa.use(router.routes())
5270

packages/point-of-sale/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Config } from './config/app'
55
import { App, AppServices } from './app'
66
import createLogger from 'pino'
77
import { createMerchantService } from './merchant/service'
8+
import { createMerchantRoutes } from './merchant/routes'
89

910
export function initIocContainer(
1011
config: typeof Config
@@ -65,6 +66,13 @@ export function initIocContainer(
6566
return createMerchantService({ logger, knex })
6667
})
6768

69+
container.singleton('merchantRoutes', async (deps) => {
70+
return createMerchantRoutes({
71+
logger: await deps.use('logger'),
72+
merchantService: await deps.use('merchantService')
73+
})
74+
})
75+
6876
return container
6977
}
7078

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export class POSMerchantRouteError extends Error {
2+
public status: number
3+
public details?: Record<string, unknown>
4+
5+
constructor(
6+
status: number,
7+
message: string,
8+
details?: Record<string, unknown>
9+
) {
10+
super(message)
11+
this.name = 'POSMerchantRouteError'
12+
this.status = status
13+
this.details = details
14+
}
15+
}

packages/point-of-sale/src/merchant/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ export class Merchant extends BaseModel {
2020
})
2121

2222
public name!: string
23-
public deletedAt!: Date | null
23+
public deletedAt?: Date | null
2424
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { IocContract } from '@adonisjs/fold'
2+
import { createContext } from '../tests/context'
3+
import { createTestApp, TestContainer } from '../tests/app'
4+
import { Config } from '../config/app'
5+
import { initIocContainer } from '..'
6+
import { AppServices } from '../app'
7+
import { truncateTables } from '../tests/tableManager'
8+
import {
9+
CreateMerchantContext,
10+
MerchantRoutes,
11+
createMerchantRoutes
12+
} from './routes'
13+
import { MerchantService } from './service'
14+
15+
describe('Merchant Routes', (): void => {
16+
let deps: IocContract<AppServices>
17+
let appContainer: TestContainer
18+
let merchantRoutes: MerchantRoutes
19+
let merchantService: MerchantService
20+
21+
beforeAll(async (): Promise<void> => {
22+
deps = initIocContainer(Config)
23+
appContainer = await createTestApp(deps)
24+
merchantService = await deps.use('merchantService')
25+
const logger = await deps.use('logger')
26+
27+
merchantRoutes = createMerchantRoutes({
28+
merchantService,
29+
logger
30+
})
31+
})
32+
33+
afterEach(async (): Promise<void> => {
34+
await truncateTables(deps)
35+
})
36+
37+
afterAll(async (): Promise<void> => {
38+
await appContainer.shutdown()
39+
})
40+
41+
describe('create', (): void => {
42+
test('Creates a merchant', async (): Promise<void> => {
43+
const merchantData = {
44+
name: 'Test Merchant'
45+
}
46+
47+
const ctx = createContext<CreateMerchantContext>(
48+
{
49+
headers: {
50+
Accept: 'application/json',
51+
'Content-Type': 'application/json'
52+
}
53+
},
54+
{}
55+
)
56+
ctx.request.body = merchantData
57+
58+
await merchantRoutes.create(ctx)
59+
60+
expect(ctx.status).toBe(200)
61+
expect(ctx.response.body).toEqual({
62+
id: expect.any(String),
63+
name: merchantData.name
64+
})
65+
})
66+
})
67+
})
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { AppContext } from '../app'
2+
import { BaseService } from '../shared/baseService'
3+
import { MerchantService } from './service'
4+
import { POSMerchantRouteError } from './errors'
5+
6+
interface ServiceDependencies extends BaseService {
7+
merchantService: MerchantService
8+
}
9+
10+
type CreateMerchantRequest = Exclude<AppContext['request'], 'body'> & {
11+
body: {
12+
name: string
13+
}
14+
}
15+
16+
export type CreateMerchantContext = Exclude<AppContext, 'request'> & {
17+
request: CreateMerchantRequest
18+
}
19+
20+
export interface MerchantRoutes {
21+
create(ctx: CreateMerchantContext): Promise<void>
22+
}
23+
24+
export function createMerchantRoutes(
25+
deps_: ServiceDependencies
26+
): MerchantRoutes {
27+
const log = deps_.logger.child({
28+
service: 'MerchantRoutes'
29+
})
30+
31+
const deps = {
32+
...deps_,
33+
logger: log
34+
}
35+
36+
return {
37+
create: (ctx: CreateMerchantContext) => createMerchant(deps, ctx)
38+
}
39+
}
40+
41+
async function createMerchant(
42+
deps: ServiceDependencies,
43+
ctx: CreateMerchantContext
44+
): Promise<void> {
45+
const { body } = ctx.request
46+
try {
47+
const merchant = await deps.merchantService.create(body.name)
48+
49+
ctx.status = 200
50+
ctx.body = { id: merchant.id, name: merchant.name }
51+
} catch (err) {
52+
throw new POSMerchantRouteError(400, 'Could not create merchant', { err })
53+
}
54+
}

packages/point-of-sale/src/merchant/service.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ describe('Merchant Service', (): void => {
3636
describe('create', (): void => {
3737
test('creates a merchant', async (): Promise<void> => {
3838
const merchant = await merchantService.create('Test merchant')
39-
expect(merchant).toEqual({ id: merchant.id, name: 'Test merchant' })
39+
expect(merchant).toMatchObject({
40+
name: 'Test merchant',
41+
createdAt: expect.any(Date),
42+
updatedAt: expect.any(Date)
43+
})
44+
expect(typeof merchant.id).toBe('string')
45+
expect(merchant.deletedAt).toBeUndefined()
4046
})
4147
})
4248

packages/point-of-sale/src/shared/baseModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ export abstract class BaseModel extends PaginationModel {
127127
public $beforeInsert(context: QueryContext): void {
128128
super.$beforeInsert(context)
129129
this.id = this.id || uuid()
130+
this.createdAt = new Date()
131+
this.updatedAt = new Date()
130132
}
131133

132134
public $beforeUpdate(_opts: ModelOptions, _queryContext: QueryContext): void {

0 commit comments

Comments
 (0)