Skip to content

Commit 0a11548

Browse files
authored
feat(card): add model and card endpoints (#2144)
* feat(card): add model and card endpoints * fix: remove check * fix: fetch wallet address
1 parent fa2182f commit 0a11548

File tree

7 files changed

+394
-1
lines changed

7 files changed

+394
-1
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
exports.up = function (knex) {
6+
return knex.schema.createTable('cards', (table) => {
7+
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'))
8+
9+
table.uuid('userId').notNullable()
10+
table.foreign('userId').references('users.id')
11+
12+
table.uuid('accountId').notNullable()
13+
table.foreign('accountId').references('accounts.id')
14+
15+
table.uuid('walletAddressId').notNullable()
16+
table.foreign('walletAddressId').references('walletAddresses.id')
17+
18+
table.string('publicKey')
19+
table
20+
.enum('status', ['ORDERED', 'INACTIVE', 'ACTIVE', 'FROZEN', 'TERMINATED'])
21+
.notNullable()
22+
23+
table.timestamp('createdAt').notNullable()
24+
table.timestamp('updatedAt').notNullable()
25+
})
26+
}
27+
28+
/**
29+
* @param { import("knex").Knex } knex
30+
* @returns { Promise<void> }
31+
*/
32+
exports.down = function (knex) {
33+
return knex.schema.dropTableIfExists('cards')
34+
}

packages/wallet/backend/src/app.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export class App {
161161
: undefined
162162
const gateHubController = this.container.resolve('gateHubController')
163163
const cardController = this.container.resolve('cardController')
164+
const interledgerCardController = this.container.resolve(
165+
'interledgerCardController'
166+
)
164167

165168
app.use(
166169
cors({
@@ -414,6 +417,29 @@ export class App {
414417
cardController.closeCard
415418
)
416419

420+
// Interledger cards
421+
router.get('/cards', isAuth, interledgerCardController.list)
422+
423+
router.get('/cards/:cardId', isAuth, interledgerCardController.getById)
424+
425+
router.post('/cards', isAuth, interledgerCardController.create)
426+
427+
router.put(
428+
'/cards/:cardId/freeze',
429+
isAuth,
430+
interledgerCardController.freeze
431+
)
432+
router.put(
433+
'/cards/:cardId/unfreeze',
434+
isAuth,
435+
interledgerCardController.unfreeze
436+
)
437+
router.delete(
438+
'/cards/:cardId/terminate',
439+
isAuth,
440+
interledgerCardController.terminate
441+
)
442+
417443
// Return an error for invalid routes
418444
router.use('*', (req: Request, res: CustomResponse) => {
419445
const e = Error(`Requested path ${req.path} was not found`)

packages/wallet/backend/src/createContainer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import { CardController } from './card/controller'
5555
import { CardService } from './card/service'
5656
import { StripeController } from './stripe-integration/controller'
5757
import { StripeService } from './stripe-integration/service'
58+
import { InterledgerCardController } from '@/interledgerCard/controller'
59+
import { InterledgerCardService } from '@/interledgerCard/service'
5860

5961
export interface Cradle {
6062
env: Env
@@ -100,6 +102,8 @@ export interface Cradle {
100102
gateHubService: GateHubService
101103
cardService: CardService
102104
cardController: CardController
105+
interledgerCardService: InterledgerCardService
106+
interledgerCardController: InterledgerCardController
103107
}
104108

105109
export async function createContainer(
@@ -160,7 +164,9 @@ export async function createContainer(
160164
gateHubClient: asClass(GateHubClient).singleton(),
161165
gateHubController: asClass(GateHubController).singleton(),
162166
gateHubService: asClass(GateHubService).singleton(),
163-
cardController: asClass(CardController).singleton()
167+
cardController: asClass(CardController).singleton(),
168+
interledgerCardService: asClass(InterledgerCardService).singleton(),
169+
interledgerCardController: asClass(InterledgerCardController).singleton()
164170
})
165171

166172
return container
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { NextFunction, Request } from 'express'
2+
import { validate } from '@/shared/validate'
3+
import { CreateCardResponse, InterledgerCardService } from './service'
4+
import { cardIdSchema, cardSchema } from './validation'
5+
import { Controller, toSuccessResponse } from '@shared/backend'
6+
import { Card } from '@/interledgerCard/model'
7+
8+
interface IInterledgerCardController {
9+
create: Controller<CreateCardResponse>
10+
list: Controller<Card[]>
11+
getById: Controller<Card>
12+
freeze: Controller
13+
unfreeze: Controller
14+
terminate: Controller
15+
}
16+
17+
export class InterledgerCardController implements IInterledgerCardController {
18+
constructor(private interledgerCardService: InterledgerCardService) {}
19+
20+
create = async (
21+
req: Request,
22+
res: CustomResponse<Card>,
23+
next: NextFunction
24+
) => {
25+
try {
26+
const userId = req.session.user.id
27+
const {
28+
body: { accountId, walletAddressId }
29+
} = await validate(cardSchema, req)
30+
31+
const createAccountResult = await this.interledgerCardService.create({
32+
userId,
33+
accountId,
34+
walletAddressId
35+
})
36+
37+
res.status(200).json(toSuccessResponse(createAccountResult))
38+
} catch (e) {
39+
next(e)
40+
}
41+
}
42+
43+
list = async (
44+
req: Request,
45+
res: CustomResponse<Card[]>,
46+
next: NextFunction
47+
) => {
48+
const userId = req.session.user.id
49+
50+
try {
51+
const accounts = await this.interledgerCardService.list(userId)
52+
53+
res.status(200).json(toSuccessResponse(accounts))
54+
} catch (e) {
55+
next(e)
56+
}
57+
}
58+
59+
getById = async (
60+
req: Request,
61+
res: CustomResponse<Card>,
62+
next: NextFunction
63+
) => {
64+
const userId = req.session.user.id
65+
const {
66+
params: { cardId }
67+
} = await validate(cardIdSchema, req)
68+
69+
try {
70+
const getAccountsResult = await this.interledgerCardService.getById(
71+
userId,
72+
cardId
73+
)
74+
75+
res.status(200).json(toSuccessResponse(getAccountsResult))
76+
} catch (e) {
77+
next(e)
78+
}
79+
}
80+
81+
freeze = async (req: Request, res: CustomResponse, next: NextFunction) => {
82+
try {
83+
const userId = req.session.user.id
84+
const {
85+
params: { cardId }
86+
} = await validate(cardIdSchema, req)
87+
88+
await this.interledgerCardService.freeze(userId, cardId)
89+
90+
res.status(200).json(toSuccessResponse())
91+
} catch (e) {
92+
next(e)
93+
}
94+
}
95+
96+
unfreeze = async (req: Request, res: CustomResponse, next: NextFunction) => {
97+
try {
98+
const userId = req.session.user.id
99+
const {
100+
params: { cardId }
101+
} = await validate(cardIdSchema, req)
102+
103+
await this.interledgerCardService.unfreeze(userId, cardId)
104+
105+
res.status(200).json(toSuccessResponse())
106+
} catch (e) {
107+
next(e)
108+
}
109+
}
110+
111+
terminate = async (req: Request, res: CustomResponse, next: NextFunction) => {
112+
try {
113+
const userId = req.session.user.id
114+
const {
115+
params: { cardId }
116+
} = await validate(cardIdSchema, req)
117+
118+
await this.interledgerCardService.terminate(userId, cardId)
119+
120+
res.status(200).json(toSuccessResponse())
121+
} catch (e) {
122+
next(e)
123+
}
124+
}
125+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { WalletAddress } from '@/walletAddress/model'
2+
import { User } from '@/user/model'
3+
import { Model } from 'objection'
4+
import { BaseModel } from '@shared/backend'
5+
import { Account } from '@/account/model'
6+
7+
export type CardStatusType =
8+
| 'ORDERED'
9+
| 'INACTIVE'
10+
| 'ACTIVE'
11+
| 'FROZEN'
12+
| 'TERMINATED'
13+
14+
export class Card extends BaseModel {
15+
static tableName = 'cards'
16+
17+
public readonly accountId!: string
18+
public readonly userId!: string
19+
public readonly walletAddressId!: string
20+
public readonly publicKey?: string | null
21+
public readonly status!: CardStatusType
22+
23+
public user!: User
24+
public account!: Account
25+
public walletAddress!: WalletAddress
26+
27+
static relationMappings = () => ({
28+
user: {
29+
relation: Model.BelongsToOneRelation,
30+
modelClass: User,
31+
join: {
32+
from: 'cards.userId',
33+
to: 'users.id'
34+
}
35+
},
36+
walletAddress: {
37+
relation: Model.BelongsToOneRelation,
38+
modelClass: WalletAddress,
39+
join: {
40+
from: 'cards.walletAddressId',
41+
to: 'walletAddresses.id'
42+
}
43+
},
44+
account: {
45+
relation: Model.BelongsToOneRelation,
46+
modelClass: Account,
47+
join: {
48+
from: 'cards.accountId',
49+
to: 'account.id'
50+
}
51+
}
52+
})
53+
}

0 commit comments

Comments
 (0)