Skip to content

Commit 86ce1bc

Browse files
feat(pos): create merchant route (#3538)
* post merchants route --------- Co-authored-by: Nathan Lie <lie4nathan@gmail.com>
1 parent 35b3a78 commit 86ce1bc

File tree

9 files changed

+162
-3
lines changed

9 files changed

+162
-3
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/src/app.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Koa, { DefaultState } from 'koa'
66
import Router from '@koa/router'
77
import bodyParser from 'koa-bodyparser'
88
import cors from '@koa/cors'
9+
<<<<<<< HEAD
910
import {
1011
GetPaymentsContext,
1112
PaymentContext,
@@ -16,12 +17,19 @@ import {
1617
WebhookHandlerRoutes
1718
} from './webhook-handlers/routes'
1819
import { webhookHttpSigMiddleware } from './webhook-handlers/middleware'
20+
=======
21+
import { CreateMerchantContext, MerchantRoutes } from './merchant/routes'
22+
>>>>>>> a20fa7ae9 (feat(pos): create merchant route (#3538))
1923

2024
export interface AppServices {
2125
logger: Promise<Logger>
2226
config: Promise<IAppConfig>
27+
<<<<<<< HEAD
2328
paymentRoutes: Promise<PaymentRoutes>
2429
webhookHandlerRoutes: Promise<WebhookHandlerRoutes>
30+
=======
31+
merchantRoutes: Promise<MerchantRoutes>
32+
>>>>>>> a20fa7ae9 (feat(pos): create merchant route (#3538))
2533
}
2634

2735
export type AppContainer = IocContract<AppServices>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ export function initIocContainer(
153153
})
154154
})
155155

156+
container.singleton('merchantRoutes', async (deps) => {
157+
return createMerchantRoutes({
158+
logger: await deps.use('logger'),
159+
merchantService: await deps.use('merchantService')
160+
})
161+
})
162+
156163
return container
157164
}
158165

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)