Skip to content

Commit

Permalink
feat(user): transfer a wallet from anonymous user to a logged in one
Browse files Browse the repository at this point in the history
if the least doesn't have any yet

refs #247
  • Loading branch information
ygrishajev committed Aug 20, 2024
1 parent fd92d15 commit edf3d3a
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 36 deletions.
3 changes: 3 additions & 0 deletions apps/api/drizzle/0004_spooky_cerise.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE "user_wallets" ADD COLUMN "created_at" timestamp DEFAULT now();--> statement-breakpoint
ALTER TABLE "user_wallets" ADD COLUMN "updated_at" timestamp DEFAULT now();--> statement-breakpoint
ALTER TABLE "userSetting" ADD COLUMN "created_at" timestamp DEFAULT now();
214 changes: 214 additions & 0 deletions apps/api/drizzle/meta/0004_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
{
"id": "43dc008c-31b1-4d9b-871f-21b6991441cc",
"prevId": "68facf51-a439-434a-a0c7-1e9c258d57df",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.user_wallets": {
"name": "user_wallets",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"address": {
"name": "address",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "varchar",
"primaryKey": false,
"notNull": false
},
"deployment_allowance": {
"name": "deployment_allowance",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": true,
"default": "'0.00'"
},
"fee_allowance": {
"name": "fee_allowance",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": true,
"default": "'0.00'"
},
"trial": {
"name": "trial",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"user_wallets_user_id_userSetting_id_fk": {
"name": "user_wallets_user_id_userSetting_id_fk",
"tableFrom": "user_wallets",
"tableTo": "userSetting",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_wallets_user_id_unique": {
"name": "user_wallets_user_id_unique",
"nullsNotDistinct": false,
"columns": [
"user_id"
]
},
"user_wallets_address_unique": {
"name": "user_wallets_address_unique",
"nullsNotDistinct": false,
"columns": [
"address"
]
}
}
},
"public.userSetting": {
"name": "userSetting",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuid_generate_v4()"
},
"userId": {
"name": "userId",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"emailVerified": {
"name": "emailVerified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"stripeCustomerId": {
"name": "stripeCustomerId",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"bio": {
"name": "bio",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscribedToNewsletter": {
"name": "subscribedToNewsletter",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"youtubeUsername": {
"name": "youtubeUsername",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"twitterUsername": {
"name": "twitterUsername",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"githubUsername": {
"name": "githubUsername",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"userSetting_userId_unique": {
"name": "userSetting_userId_unique",
"nullsNotDistinct": false,
"columns": [
"userId"
]
},
"userSetting_username_unique": {
"name": "userSetting_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
7 changes: 7 additions & 0 deletions apps/api/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
"when": 1722946059197,
"tag": "0003_magenta_mandarin",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1724065113448,
"tag": "0004_spooky_cerise",
"breakpoints": true
}
]
}
6 changes: 4 additions & 2 deletions apps/api/env/.env.functional.test
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
AkashSandboxDatabaseCS=postgres://postgres:password@localhost:5432/console-akash-sandbox
UserDatabaseCS=postgres://postgres:password@localhost:5432/console-users
POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
MASTER_WALLET_MNEMONIC="motion isolate mother convince snack twenty tumble boost elbow bundle modify balcony"
NETWORK=sandbox
POSTGRES_DB_URI=postgres://postgres:[email protected]:5432/console-users
RPC_NODE_ENDPOINT=https://rpc.sandbox-01.aksh.pw:443
TRIAL_DEPLOYMENT_ALLOWANCE_AMOUNT=20000000
DEPLOYMENT_ALLOWANCE_REFILL_AMOUNT=20000000
DEPLOYMENT_ALLOWANCE_REFILL_THRESHOLD=2000000
TRIAL_FEES_ALLOWANCE_AMOUNT=5000000
FEE_ALLOWANCE_REFILL_AMOUNT=5000000
FEE_ALLOWANCE_REFILL_THRESHOLD=500000
DEPLOYMENT_GRANT_DENOM=ibc/12C6A0C374171B595A0A9E18B83FA09D295FB1F2D8C6DAA3AC28683471752D84
DEPLOYMENT_GRANT_DENOM=uakt
LOG_LEVEL=debug
BILLING_ENABLED=true
ANONYMOUS_USER_TOKEN_SECRET=ANONYMOUS_USER_TOKEN_SECRET
3 changes: 2 additions & 1 deletion apps/api/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ module.exports = {
displayName: "functional",
...common,
testMatch: ["<rootDir>/test/functional/**/*.spec.ts"],
setupFilesAfterEnv: ["./test/setup-functional-tests.ts"]
setupFilesAfterEnv: ["./test/setup-functional-tests.ts"],
setupFiles: ["./test/setup-functional-env.ts"]
}
]
};
4 changes: 2 additions & 2 deletions apps/api/src/auth/services/auth-token/auth-token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class AuthTokenService {

constructor(@InjectAuthConfig() private readonly config: AuthConfig) {}

signTokenFor(input: { userId: string }): string {
return jwt.sign({ sub: input.userId, type: "ANONYMOUS" }, this.config.ANONYMOUS_USER_TOKEN_SECRET);
signTokenFor(input: { id: string }): string {
return jwt.sign({ sub: input.id, type: "ANONYMOUS" }, this.config.ANONYMOUS_USER_TOKEN_SECRET);
}

async getValidUserId(bearer: string): Promise<string | undefined> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { boolean, numeric, pgTable, serial, uuid, varchar } from "drizzle-orm/pg-core";
import { boolean, numeric, pgTable, serial, timestamp, uuid, varchar } from "drizzle-orm/pg-core";

import { userSchema } from "@src/user/model-schemas";

Expand All @@ -11,7 +11,9 @@ export const userWalletSchema = pgTable("user_wallets", {
stripeCustomerId: varchar("stripe_customer_id"),
deploymentAllowance: allowance("deployment_allowance"),
feeAllowance: allowance("fee_allowance"),
isTrialing: boolean("trial").default(true)
isTrialing: boolean("trial").default(true),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow()
});

function allowance(name: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export interface ListOptions {
offset?: number;
}

interface UpdateOptions {
returning: true;
}

@singleton()
export class UserWalletRepository extends BaseRepository<UserWalletSchema> {
constructor(
Expand All @@ -52,13 +56,16 @@ export class UserWalletRepository extends BaseRepository<UserWalletSchema> {
return this.toOutput(first(await this.cursor.insert(this.schema).values(value).returning()));
}

async updateById(id: UserWalletOutput["id"], payload: Partial<UserWalletInput>, options?: { returning: true }): Promise<UserWalletOutput>;
async updateById(id: UserWalletOutput["id"], payload: Partial<UserWalletInput>, options?: UpdateOptions): Promise<UserWalletOutput>;
async updateById(id: UserWalletOutput["id"], payload: Partial<UserWalletInput>): Promise<void>;
async updateById(id: UserWalletOutput["id"], payload: Partial<UserWalletInput>, options?: { returning: boolean }): Promise<void | UserWalletOutput> {
const cursor = this.cursor
.update(this.schema)
.set(this.toInput(payload))
.where(this.whereAccessibleBy(eq(this.schema.id, id)));
async updateById(id: UserWalletOutput["id"], payload: Partial<UserWalletInput>, options?: UpdateOptions): Promise<void | UserWalletOutput> {
return this.updateBy({ id }, payload, options);
}

async updateBy(query: Partial<DbUserWalletOutput>, payload: Partial<UserWalletInput>, options?: UpdateOptions): Promise<UserWalletOutput>;
async updateBy(query: Partial<DbUserWalletOutput>, payload: Partial<UserWalletInput>): Promise<void>;
async updateBy(query: Partial<DbUserWalletOutput>, payload: Partial<UserWalletInput>, options?: UpdateOptions): Promise<void | UserWalletOutput> {
const cursor = this.cursor.update(this.schema).set(this.toInput(payload)).where(this.queryToWhere(query));

if (options?.returning) {
const items = await cursor.returning();
Expand All @@ -71,16 +78,20 @@ export class UserWalletRepository extends BaseRepository<UserWalletSchema> {
}

async find(query?: Partial<DbUserWalletOutput>) {
const fields = query && (Object.keys(query) as Array<keyof DbUserWalletOutput>);
const where = fields?.length ? and(...fields.map(field => eq(this.schema[field], query[field]))) : undefined;

return this.toOutputList(
await this.cursor.query.userWalletSchema.findMany({
where: this.whereAccessibleBy(where)
where: this.queryToWhere(query)
})
);
}

private queryToWhere(query: Partial<DbUserWalletOutput>) {
const fields = query && (Object.keys(query) as Array<keyof DbUserWalletOutput>);
const where = fields?.length ? and(...fields.map(field => eq(this.schema[field], query[field]))) : undefined;

return this.whereAccessibleBy(where);
}

async findDrainingWallets(thresholds = { fee: 0, deployment: 0 }, options?: Pick<ListOptions, "limit">) {
const where = or(lte(this.schema.deploymentAllowance, thresholds.deployment.toString()), lte(this.schema.feeAllowance, thresholds.fee.toString()));

Expand Down Expand Up @@ -117,7 +128,8 @@ export class UserWalletRepository extends BaseRepository<UserWalletSchema> {

private toInput({ deploymentAllowance, feeAllowance, ...input }: UserWalletInput): DbUserWalletInput {
const dbInput: DbUserWalletInput = {
...input
...input,
updatedAt: new Date()
};

if (deploymentAllowance) {
Expand Down
Loading

0 comments on commit edf3d3a

Please sign in to comment.