Skip to content

Commit

Permalink
WIP refactor(billing): extract trial wallet creation to a service
Browse files Browse the repository at this point in the history
also refactor module imports

refs #247
  • Loading branch information
ygrishajev committed Jul 4, 2024
1 parent bacc119 commit a18a906
Show file tree
Hide file tree
Showing 18 changed files with 71 additions and 39 deletions.
1 change: 0 additions & 1 deletion apps/api/src/billing/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ dotenv.config();

const envSchema = z.object({
MASTER_WALLET_MNEMONIC: z.string(),
POSTGRES_DB_URI: z.string(),
NETWORK: z.enum(["mainnet", "testnet", "sandbox"]),
RPC_NODE_ENDPOINT: z.string(),
TRIAL_ALLOWANCE_EXPIRATION_DAYS: z.number({ coerce: true }).default(14),
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/billing/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ export const config = {
...env,
USDC_IBC_DENOMS
};
export type BillingConfig = typeof config;
12 changes: 5 additions & 7 deletions apps/api/src/billing/controllers/wallet/WalletController.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { PromisePool } from "@supercharge/promise-pool";
import { singleton } from "tsyringe";

import { CreateWalletInput, CreateWalletOutput } from "@src/billing";
import { UserWalletRepository } from "@src/billing/repositories";
import { WalletManager } from "@src/billing/services";
import { TxManager, WithTransaction } from "@src/core/services/tx-manager/TxManager";
import { CreateWalletInput, CreateWalletOutput } from "@src/billing/routes";
import { WalletInitializer, WalletManager } from "@src/billing/services";
import { WithTransaction } from "@src/core/services";

@singleton()
export class WalletController {
constructor(
private readonly walletManager: WalletManager,
private readonly userWalletRepository: UserWalletRepository,
private readonly txManager: TxManager
private readonly walletInitializer: WalletInitializer
) {}

@WithTransaction()
async create({ userId }: CreateWalletInput): Promise<CreateWalletOutput> {
const userWallet = await this.userWalletRepository.create({ userId });
const wallet = await this.walletManager.createAndAuthorizeTrialSpending({ addressIndex: userWallet.id });
await this.userWalletRepository.updateById(userWallet.id, { address: wallet.address });
await this.walletInitializer.initialize(userId);
}

async refillAll() {
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/billing/model-schemas/userWalletSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ export const userWalletSchema = pgTable("user_wallets", {
.notNull()
.default("0.00")
});

export type UserWalletSchema = typeof userWalletSchema;
6 changes: 5 additions & 1 deletion apps/api/src/billing/providers/configProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { container } from "tsyringe";
import { container, inject } from "tsyringe";

import { config } from "@src/billing/config";

export const BILLING_CONFIG = "BILLING_CONFIG";

container.register(BILLING_CONFIG, { useValue: config });

export type BillingConfig = typeof config;

export const InjectBillingConfig = () => inject(BILLING_CONFIG);
3 changes: 3 additions & 0 deletions apps/api/src/billing/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
import "./userWalletSchemaProvider";
import "./configProvider";

export * from "./userWalletSchemaProvider";
export * from "./configProvider";
6 changes: 5 additions & 1 deletion apps/api/src/billing/providers/userWalletSchemaProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { container } from "tsyringe";
import { container, inject } from "tsyringe";

import { userWalletSchema } from "@src/billing/model-schemas";

export const USER_WALLET_SCHEMA = "USER_WALLET_SCHEMA";

container.register(USER_WALLET_SCHEMA, { useValue: userWalletSchema });

export type UserWalletSchema = typeof userWalletSchema;

export const InjectUserWalletSchema = () => inject(USER_WALLET_SCHEMA);
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { eq } from "drizzle-orm";
import first from "lodash/first";
import { inject, singleton } from "tsyringe";
import { singleton } from "tsyringe";

import type { UserWalletSchema } from "@src/billing/model-schemas/userWalletSchema";
import { USER_WALLET_SCHEMA } from "@src/billing/providers/userWalletSchemaProvider";
import { InjectUserWalletSchema, UserWalletSchema } from "@src/billing/providers";
import { ApiPgDatabase, InjectPg } from "@src/core/providers";
import { TxManager } from "@src/core/services/tx-manager/TxManager";
import { TxManager } from "@src/core/services";

export type UserInput = Partial<UserWalletSchema["$inferInsert"]>;

@singleton()
export class UserWalletRepository {
constructor(
@InjectPg() private readonly pg: ApiPgDatabase,
@inject(USER_WALLET_SCHEMA) private readonly userWallet: UserWalletSchema,
@InjectUserWalletSchema() private readonly userWallet: UserWalletSchema,
private readonly txManager: TxManager
) {}

async create(input: Pick<UserWalletSchema["$inferInsert"], "userId" | "address">) {
async create(input: Pick<UserInput, "userId" | "address">) {
const pg = this.txManager.getPgTx() || this.pg;
return first(
await pg
Expand All @@ -30,7 +31,7 @@ export class UserWalletRepository {

async updateById<R extends boolean>(
id: UserWalletSchema["$inferSelect"]["id"],
payload: Partial<UserWalletSchema["$inferInsert"]>,
payload: Partial<UserInput>,
options?: { returning: R }
): Promise<R extends true ? UserWalletSchema["$inferSelect"] : void> {
const pg = this.txManager.getPgTx() || this.pg;
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/billing/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./wallet-manager/WalletManager";
export * from "./rpc-message-service/RpcMessageService";
export * from "./wallet-initializer/WalletInitializer";
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { singleton } from "tsyringe";

import { UserInput, UserWalletRepository } from "@src/billing/repositories";
import { WalletManager } from "@src/billing/services";
import { WithTransaction } from "@src/core/services";

@singleton()
export class WalletInitializer {
constructor(
private readonly walletManager: WalletManager,
private readonly userWalletRepository: UserWalletRepository
) {}

@WithTransaction()
async initialize(userId: UserInput["userId"]) {
const userWallet = await this.userWalletRepository.create({ userId });
const wallet = await this.walletManager.createAndAuthorizeTrialSpending({ addressIndex: userWallet.id });
await this.userWalletRepository.updateById(userWallet.id, { address: wallet.address });
}
}
9 changes: 4 additions & 5 deletions apps/api/src/billing/services/wallet-manager/WalletManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { stringToPath } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { calculateFee, GasPrice, SigningStargateClient } from "@cosmjs/stargate";
import add from "date-fns/add";
import { inject, singleton } from "tsyringe";
import { singleton } from "tsyringe";

import type { BillingConfig } from "@src/billing/config";
import { BILLING_CONFIG } from "@src/billing/providers/configProvider";
import { BillingConfig, InjectBillingConfig } from "@src/billing/providers";
import { RpcMessageService } from "@src/billing/services/rpc-message-service/RpcMessageService";
import { ContextualLogger } from "@src/core/services/contextual-logger/ContextualLogger";
import { ContextualLogger } from "@src/core/services";

interface SpendingAuthorizationOptions {
address: string;
Expand All @@ -31,7 +30,7 @@ export class WalletManager {
private readonly logger = new ContextualLogger({ context: WalletManager.name });

constructor(
@inject(BILLING_CONFIG) private readonly config: BillingConfig,
@InjectBillingConfig() private readonly config: BillingConfig,
private readonly rpcMessageService: RpcMessageService
) {}

Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ dotenv.config({ path: ".env.local" });
dotenv.config();

const envSchema = z.object({
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).optional().default("info")
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).optional().default("info"),
POSTGRES_DB_URI: z.string()
});

export const env = envSchema.parse(process.env);
7 changes: 6 additions & 1 deletion apps/api/src/core/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
import "./postgres/postgres";
export * from "./tx-manager/TxManager";
export * from "./logger/Logger";
export * from "./contextual-logger/ContextualLogger";
export * from "./postgres/postgres";
export * from "./log-http-requests/logHttpRequests";
export * from "./request-storage/RequestStorage";
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Context, Next } from "hono";

import { ContextualLogger } from "@src/core/services/contextual-logger/ContextualLogger";
import { ContextualLogger } from "@src/core/services";

export const logHttpRequests = () => async (c: Context, next: Next) => {
const logger = new ContextualLogger({ context: "HTTP" });
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/core/services/logger/Logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pino, { Bindings } from "pino";

import { env } from "@src/core/config/env";
import { config } from "@src/core/config";

export class Logger {
private readonly pino: pino.Logger;
Expand All @@ -10,7 +10,7 @@ export class Logger {
}

constructor(bindings?: Bindings) {
this.pino = pino({ level: env.LOG_LEVEL });
this.pino = pino({ level: config.LOG_LEVEL });

if (bindings) {
this.pino = this.logger.child(bindings);
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/core/services/postgres/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg";

import * as billingSchemas from "@src/billing/model-schemas";
import { config } from "@src/core/config";
import { ContextualLogger } from "@src/core/services/contextual-logger/ContextualLogger";
import { env } from "@src/utils/env";

const pool = new Pool({
connectionString: env.UserDatabaseCS
connectionString: config.POSTGRES_DB_URI
});

class ContextualLogWriter implements LogWriter {
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/core/services/tx-manager/TxManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { PgTransaction } from "drizzle-orm/pg-core";
import { AsyncLocalStorage } from "node:async_hooks";
import { container, singleton } from "tsyringe";

import { ApiPgDatabase, InjectPg } from "@src/core/providers/PostgresProvider";
import type { ApiPgSchema } from "@src/core/services/postgres/postgres";
import { ApiPgDatabase, InjectPg } from "@src/core/providers";
import type { ApiPgSchema } from "@src/core/services";

type TxType = "PG_TX";

Expand Down
9 changes: 4 additions & 5 deletions apps/api/test/functional/create-wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { eq } from "drizzle-orm";
import { container } from "tsyringe";

import { app, initDb } from "@src/app";
import type { UserWalletSchema } from "@src/billing/model-schemas";
import { USER_WALLET_SCHEMA } from "@src/billing/providers/userWalletSchemaProvider";
import { ApiPgDatabase, POSTGRES_DB } from "@src/core/providers";
import { USER_WALLET_SCHEMA, UserWalletSchema } from "@src/billing/providers";
import { ApiPgDatabase, POSTGRES_DB } from "@src/core/providers/PostgresProvider";
import { closeConnections } from "@src/db/dbConnection";

jest.setTimeout(30000);
describe("app", () => {
describe("wallets", () => {
const schema = container.resolve<UserWalletSchema>(USER_WALLET_SCHEMA);
const db = container.resolve<ApiPgDatabase>(POSTGRES_DB);
const userWalletsTable = db.query.userWalletSchema;
Expand All @@ -27,7 +26,7 @@ describe("app", () => {
});

describe("POST /v1/wallets", () => {
it("should create a wallet and assign it to a user", async () => {
it("should create a wallet for a user", async () => {
const userId = faker.string.uuid();
const res = await app.request("/v1/wallets", {
method: "POST",
Expand Down

0 comments on commit a18a906

Please sign in to comment.