Skip to content
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

feat(wm): update webhook handling and transactions service #903

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b80c7c5
fix(wallet-backend): env parsing
beniaminmunteanu Oct 16, 2023
0b7816b
feat(wallet-backend) add caching
beniaminmunteanu Oct 16, 2023
4dadcff
fix(wallet-backend): docker-compose dev variable names
beniaminmunteanu Oct 16, 2023
17a36c7
feat(wallet-backend): introduce web monetized payment pointers module
beniaminmunteanu Oct 16, 2023
f909292
feat(wallet-backend): update balance now only does balance
beniaminmunteanu Oct 17, 2023
64367ec
feat(wallet-backend): WM payment pointers:
beniaminmunteanu Oct 17, 2023
bb3a4d2
chore(wallet-backend): remove comment
beniaminmunteanu Oct 17, 2023
8523383
fix(wallet-backend): wm get shouldn't throw
beniaminmunteanu Oct 17, 2023
2c46780
chore(wallet-backend): wmPp get method refactor
beniaminmunteanu Oct 17, 2023
1b76d70
move wmPaymentPointerModel into paymentPointerModule
beniaminmunteanu Oct 18, 2023
0c2d245
Merge branch 'hackathon' into wm-payment-pointers-module
beniaminmunteanu Oct 18, 2023
6184edc
move wmPaymentPointers to PaymentPointers
beniaminmunteanu Oct 18, 2023
4750a04
add REDIS_URL to prod env.example
beniaminmunteanu Oct 18, 2023
5b8422b
cache service now has a generic type across the cache
beniaminmunteanu Oct 18, 2023
935e139
rename CacheService to Cache
beniaminmunteanu Oct 18, 2023
95d2d89
list now returns 2 arrays of wm or not wm paymentPointers
beniaminmunteanu Oct 18, 2023
519d563
update frontend to match list response
beniaminmunteanu Oct 18, 2023
e49b8c7
remove leftover wmPaymentPointers relation on getAccounts
beniaminmunteanu Oct 18, 2023
d031675
format
beniaminmunteanu Oct 18, 2023
4597c8a
add concat on paymentPointers response to transactions list as well
beniaminmunteanu Oct 18, 2023
d4d0397
lint
beniaminmunteanu Oct 18, 2023
a8e2ef6
fix frontend error
beniaminmunteanu Oct 18, 2023
daace18
fix frontend error
beniaminmunteanu Oct 18, 2023
1a73a9a
add redis_url default value in env
beniaminmunteanu Oct 18, 2023
b43a807
accountId is now optional on Payment pointer getByID
beniaminmunteanu Oct 18, 2023
e102f6e
update balance no longer needs accoutId
beniaminmunteanu Oct 18, 2023
2654113
format & errors
beniaminmunteanu Oct 18, 2023
616ff4d
feat(wm): update webhook handling and transactions service
dragosp1011 Oct 18, 2023
3a9ca64
fix: change fetched user
dragosp1011 Oct 18, 2023
dd8ccea
chore: add redis testing container
dragosp1011 Oct 18, 2023
76c65b8
chore: fix formatting
dragosp1011 Oct 18, 2023
03b4ec0
chore: fix tests
dragosp1011 Oct 18, 2023
38aa767
fix: handle failed outgoing payments
dragosp1011 Oct 19, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/workflows/setup
- run: pnpm wallet:backend test
- run: pnpm wallet:backend test --detectOpenHandles --forceExit

test-boutique-backend:
name: BOUTIQUE - Test backend
Expand Down
6 changes: 4 additions & 2 deletions docker/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
depends_on:
- postgres
- rafiki-backend
- redis
environment:
NODE_ENV: development
PORT: 3003
Expand All @@ -45,10 +46,11 @@ services:
FROM_EMAIL: ${FROM_EMAIL}
SEND_EMAIL: ${SEND_EMAIL}
RATE_API_KEY: ${RATE_API_KEY}
BASE_ASSET_SCALE: 2,
MAX_ASSET_SCALE: 9,
BASE_ASSET_SCALE: 2
MAX_ASSET_SCALE: 9
WM_THRESHOLD: 100000000
DEBT_THRESHOLD: 5
REDIS_URL: ${REDIS_URL}
restart: always
networks:
- testnet
Expand Down
1 change: 1 addition & 0 deletions docker/prod/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ WALLET_FRONTEND_AUTH_HOST=
# WALLET BACKEND
WALLET_BACKEND_PORT=
WALLET_BACKEND_DATABASE_URL=
WALLET_BACKEND_REDIS_URL=
WALLET_BACKEND_COOKIE_NAME=
WALLET_BACKEND_COOKIE_PASSWORD=
WALLET_BACKEND_COOKIE_TTL=
Expand Down
10 changes: 9 additions & 1 deletion packages/wallet/backend/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { randomBytes } = require('crypto')
const POSTGRES_PASSWORD = 'password'
const POSTGRES_DB = randomBytes(16).toString('hex')
const POSTGRES_PORT = 5432
const REDIS_PORT = 6379

module.exports = async () => {
const container = await new GenericContainer('postgres:15')
Expand All @@ -14,9 +15,16 @@ module.exports = async () => {
.withExposedPorts(POSTGRES_PORT)
.start()

const redisContainer = await new GenericContainer('redis:7')
.withExposedPorts(REDIS_PORT)
.start()

process.env.REDIS_URL = `redis://redis:${REDIS_PORT}/0`

process.env.DATABASE_URL = `postgresql://postgres:${POSTGRES_PASSWORD}@localhost:${container.getMappedPort(
POSTGRES_PORT
)}/${POSTGRES_DB}`

global.__POSTGRES_CONTAINER__ = container
global.__TESTING_POSTGRES_CONTAINER__ = container
global.__TESTING_REDIS_CONTAINER__ = redisContainer
}
1 change: 1 addition & 0 deletions packages/wallet/backend/jest.teardown.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = async () => {
if (global.__TESTING_POSTGRES_CONTAINER__) {
await global.__TESTING_POSTGRES_CONTAINER__.stop()
await global.__TESTING_REDIS_CONTAINER__.stop()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ exports.up = function (knex) {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'))
table.uuid('paymentId').notNullable()

table.uuid('wmPaymentPointerId').notNullable()
table.foreign('wmPaymentPointerId').references('wmPaymentPointers.id')
table.uuid('paymentPointerId').notNullable()
table.foreign('paymentPointerId').references('paymentPointers.id')

table.bigint('value').notNullable()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.alterTable('paymentPointers', (table) => {
table.boolean('isWM').defaultTo(false)
table.string('assetCode', 3)
table.smallint('assetScale')
table.decimal('balance', 10, 2).defaultTo(0)
})
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.alterTable('paymentPointers', (table) => {
table.dropColumn('isWM')
table.dropColumn('assetCode')
table.dropColumn('assetScale')
table.dropColumn('balance')
})
}
5 changes: 3 additions & 2 deletions packages/wallet/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
"graphql-request": "^6.1.0",
"hash-wasm": "^4.9.0",
"helmet": "^7.0.0",
"ioredis": "^5.3.2",
"iron-session": "^6.3.1",
"knex": "^3.0.1",
"node-cache": "^5.1.2",
"objection": "^3.1.2",
"pg": "^8.11.3",
"socket.io": "^4.7.2",
"randexp": "^0.5.3",
"socket.io": "^4.7.2",
"uuid": "^9.0.1",
"winston": "^3.11.0",
"zod": "^3.22.4"
Expand All @@ -40,9 +41,9 @@
"@types/express": "^4.17.19",
"@types/jest": "^29.5.5",
"@types/node": "^18.14.0",
"@types/socket.io": "^3.0.2",
"@types/uuid": "^9.0.5",
"jest": "^29.7.0",
"@types/socket.io": "^3.0.2",
"node-mocks-http": "^1.13.0",
"testcontainers": "^10.2.1",
"ts-jest": "^29.1.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/wallet/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ import { UserController } from './user/controller'
import type { UserService } from './user/service'
import { SocketService } from './socket/service'
import { GrantService } from '@/grant/service'
import { RedisClient } from './cache/redis-client'
import { WMTransactionService } from '@/webMonetization/transaction/service'

export interface Bindings {
env: Env
logger: Logger
knex: Knex
redisClient: RedisClient
rapydClient: RapydClient
rafikiClient: RafikiClient
rafikiService: RafikiService
Expand Down Expand Up @@ -76,6 +79,7 @@ export interface Bindings {
grantService: GrantService
emailService: EmailService
socketService: SocketService
wmTransactionService: WMTransactionService
}

export class App {
Expand Down
48 changes: 48 additions & 0 deletions packages/wallet/backend/src/cache/redis-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Redis } from 'ioredis'

export type EntryOptions = {
expiry?: number
}

export interface IRedisClient {
set<T>(
key: string,
value: T | string,
options?: EntryOptions
): Promise<string>
get<T>(key: string): Promise<T | null>
delete(key: string): Promise<number>
}

export class RedisClient implements IRedisClient {
constructor(private redis: Redis) {}

async set<T>(
key: string,
value: T | string,
options?: EntryOptions
): Promise<string> {
const serializedValue =
typeof value === 'string' ? value : JSON.stringify(value)

if (options?.expiry) {
return await this.redis.set(key, serializedValue, 'EX', options.expiry)
}

return await this.redis.set(key, serializedValue)
}

async get<T>(key: string): Promise<T | null> {
const serializedValue = await this.redis.get(key)
if (serializedValue) {
return typeof serializedValue === 'string'
? (JSON.parse(serializedValue) as T)
: serializedValue
}
return null
}

async delete(key: string): Promise<number> {
return await this.redis.del(key)
}
}
37 changes: 37 additions & 0 deletions packages/wallet/backend/src/cache/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { EntryOptions, IRedisClient } from './redis-client'

export interface ICacheService<T> {
set(key: string, value: T | string): Promise<string>
get(key: string): Promise<T | null>
delete(key: string): Promise<number>
}

export class Cache<T> implements ICacheService<T> {
private namespace: string = 'Testnet:'

constructor(
private cache: IRedisClient,
namespace: string
) {
this.namespace = this.namespace + namespace + ':'
}

async set(
key: string,
value: T | string,
options?: EntryOptions
): Promise<string> {
const namespacedKey = this.namespace + key
return await this.cache.set<T>(namespacedKey, value, options)
}

async get(key: string): Promise<T | null> {
const namespacedKey = this.namespace + key
return await this.cache.get<T>(namespacedKey)
}

async delete(key: string): Promise<number> {
const namespacedKey = this.namespace + key
return await this.cache.delete(namespacedKey)
}
}
9 changes: 5 additions & 4 deletions packages/wallet/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const envSchema = z.object({
DATABASE_URL: z
.string()
.default('postgres://postgres:password@localhost:5433/wallet_backend'),
REDIS_URL: z.string().default('redis://redis:6379/0'),
COOKIE_NAME: z.string().default('testnet.cookie'),
COOKIE_PASSWORD: z
.string()
Expand All @@ -31,10 +32,10 @@ const envSchema = z.object({
.enum(['true', 'false'])
.default('false')
.transform((value) => value === 'true'),
BASE_ASSET_SCALE: z.number().nonnegative().default(2),
MAX_ASSET_SCALE: z.number().nonnegative().default(9),
WM_THRESHOLD: z.bigint().nonnegative().default(100_000_000n), // $0.1 in asset scale 9
DEBT_THRESHOLD: z.number().multipleOf(0.01).nonnegative().default(5.0) // $5.00
BASE_ASSET_SCALE: z.coerce.number().nonnegative().default(2),
MAX_ASSET_SCALE: z.coerce.number().nonnegative().default(9),
WM_THRESHOLD: z.coerce.bigint().nonnegative().default(100_000_000n), // $0.1 in asset scale 9
DEBT_THRESHOLD: z.coerce.number().multipleOf(0.01).nonnegative().default(5.0) // $5.00
})

export type Env = z.infer<typeof envSchema>
Expand Down
52 changes: 41 additions & 11 deletions packages/wallet/backend/src/createContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ import knex from 'knex'
import { SocketService } from './socket/service'
import { GrantService } from './grant/service'
import { RatesService } from './rates/service'
import { Cache } from './cache/service'
import { RedisClient } from './cache/redis-client'
import { Redis } from 'ioredis'
import { PaymentPointer } from './paymentPointer/model'
import { WMTransactionService } from '@/webMonetization/transaction/service'

export const createContainer = (config: Env): Container<Bindings> => {
const container = new Container<Bindings>()
Expand Down Expand Up @@ -179,6 +184,26 @@ export const createContainer = (config: Env): Container<Bindings> => {
async () => new RatesService({ env: await container.resolve('env') })
)

container.singleton('redisClient', async () => {
const env = await container.resolve('env')
const redis = new Redis(env.REDIS_URL)
return new RedisClient(redis)
})

container.singleton(
'paymentPointerService',
async () =>
new PaymentPointerService({
env: await container.resolve('env'),
rafikiClient: await container.resolve('rafikiClient'),
accountService: await container.resolve('accountService'),
cache: new Cache<PaymentPointer>(
await container.resolve('redisClient'),
'WMPaymentPointers'
)
})
)

container.singleton(
'rapydController',
async () =>
Expand All @@ -192,16 +217,6 @@ export const createContainer = (config: Env): Container<Bindings> => {
})
)

container.singleton(
'paymentPointerService',
async () =>
new PaymentPointerService({
env: await container.resolve('env'),
rafikiClient: await container.resolve('rafikiClient'),
accountService: await container.resolve('accountService')
})
)

container.singleton(
'paymentPointerController',
async () =>
Expand Down Expand Up @@ -270,6 +285,15 @@ export const createContainer = (config: Env): Container<Bindings> => {
})
)

container.singleton(
'wmTransactionService',
async () =>
new WMTransactionService({
logger: await container.resolve('logger'),
paymentPointerService: await container.resolve('paymentPointerService')
})
)

container.singleton('rafikiService', async () => {
const rapydClient = await container.resolve('rapydClient')
const env = await container.resolve('env')
Expand All @@ -279,6 +303,10 @@ export const createContainer = (config: Env): Container<Bindings> => {
const socketService = await container.resolve('socketService')
const userService = await container.resolve('userService')
const ratesService = await container.resolve('ratesService')
const wmTransactionService = await container.resolve('wmTransactionService')
const paymentPointerService = await container.resolve(
'paymentPointerService'
)

return new RafikiService({
rafikiClient,
Expand All @@ -288,7 +316,9 @@ export const createContainer = (config: Env): Container<Bindings> => {
logger,
transactionService,
socketService,
userService
userService,
wmTransactionService,
paymentPointerService
})
})

Expand Down
Loading