Skip to content

Commit

Permalink
Merge pull request #29 from gentlementlegen/fix/error-messages
Browse files Browse the repository at this point in the history
fix: error messages
  • Loading branch information
gentlementlegen authored Dec 12, 2024
2 parents 85c3c0c + 099ee70 commit bfd38c3
Show file tree
Hide file tree
Showing 17 changed files with 171 additions and 148 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/** linguist-generated
bun.lockb linguist-generated
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions dist/index.js

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
"commands": {
"wallet": {
"ubiquity:example": "/wallet ubq.eth",
"description": "Register your wallet address for payments.",
"description": "Register your wallet address for payments. Use '/wallet unset' to unlink your wallet.",
"parameters": {
"type": "object",
"properties": {
"walletAddress": {
"description": "Ethereum address or Ethereum Name Service",
"type": "string"
},
"unset": {
"description": "Unsets the wallet associated with a user",
"type": "boolean",
"default": false
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@sinclair/typebox": "0.34.3",
"@supabase/supabase-js": "2.43.5",
"@ubiquity-dao/rpc-handler": "1.3.0",
"@ubiquity-os/plugin-sdk": "^1.1.0",
"@ubiquity-os/plugin-sdk": "^1.1.1",
"@ubiquity-os/ubiquity-os-logger": "^1.3.2",
"commander": "12.1.0",
"dotenv": "16.4.5",
Expand Down Expand Up @@ -75,7 +75,7 @@
},
"lint-staged": {
"*.ts": [
"bun prettier --write",
"prettier --write",
"eslint --fix"
],
"src/**.{ts,json}": [
Expand Down
139 changes: 55 additions & 84 deletions src/adapters/supabase/helpers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,143 +6,140 @@ import { Super } from "./supabase";
type WalletRow = Database["public"]["Tables"]["wallets"]["Row"];
type WalletInsert = Database["public"]["Tables"]["wallets"]["Insert"];
type UserRow = Database["public"]["Tables"]["users"]["Row"];
type UserWithWallet = (UserRow & { wallets: WalletRow | null })[];
type UserWithWallet = UserRow & { wallets: WalletRow | null };

export class Wallet extends Super {
constructor(supabase: SupabaseClient<Database>, context: Context) {
super(supabase, context);
}

public async getAddress(id: number) {
const userWithWallet = await this._getUserWithWallet(id);
const userWithWallet = await this._getUserFromWalletId(id);
if (!userWithWallet) return null;
return this._validateAndGetWalletAddress(userWithWallet);
}

public async upsertWalletAddress(context: Context, address: string) {
const payload = context.payload;

const userData = await this._getUserData(payload);
const registeredWalletData = await this._getRegisteredWalletData(userData);

const locationMetaData = this._getLocationMetaData(payload);
const registeredWalletData = await this._getRegisteredWalletData(address);

if (!registeredWalletData) {
await this._registerNewWallet(context, {
address,
locationMetaData,
payload,
});
} else {
await this._updateExistingWallet(context, {
address,
locationMetaData,
payload,
walletData: registeredWalletData,
});
}
}

private async _getUserWithWallet(id: number) {
const { data, error } = await this.supabase.from("users").select("*, wallets(*)").filter("id", "eq", id);
if (error) throw error;
public async unlinkWalletFromUserId(userId: number) {
const userData = await this._getUserFromId(userId);

if (!userData?.wallet_id) {
throw this.context.logger.error("The user does not have an associated wallet to unlink");
}

const { error } = await this.supabase.from("users").update({ wallet_id: null }).eq("id", userData.id);

if (error) {
throw this.context.logger.error(`Could not unlink the wallet.`, error);
}
}

private async _getUserFromWalletId(id: number) {
const { data, error } = await this.supabase.from("users").select("*, wallets(*)").filter("wallet_id", "eq", id).maybeSingle();
if (error) throw this.context.logger.error(`Could not get the user from its wallet id.`, error);
return data;
}

private async _getUserFromId(id: number) {
const { data, error } = await this.supabase.from("users").select("*, wallets(*)").filter("id", "eq", id).maybeSingle();
if (error) throw this.context.logger.error(`Could not get the user from its id.`, error);
return data;
}

private _validateAndGetWalletAddress(userWithWallet: UserWithWallet): string {
if (userWithWallet[0]?.wallets?.address === undefined) throw new Error("Wallet address is undefined");
if (userWithWallet[0]?.wallets?.address === null) throw new Error("Wallet address is null");
return userWithWallet[0]?.wallets?.address;
if (userWithWallet?.wallets?.address === undefined) throw this.context.logger.error("The wallet address is undefined");
if (userWithWallet?.wallets?.address === null) throw this.context.logger.error("The wallet address is null");
return userWithWallet?.wallets?.address;
}

private async _checkIfUserExists(userId: number) {
const { data, error } = await this.supabase.from("users").select("*").eq("id", userId).maybeSingle();
if (error) throw error;
if (error) throw this.context.logger.error(`Could not check if the user exists.`, error);
return data as UserRow;
}

private async _getUserData(payload: Context["payload"]) {
let userData = await this._checkIfUserExists(payload.sender.id);
if (!userData) {
const user = payload.sender;
userData = await this._registerNewUser(user, this._getLocationMetaData(payload));
userData = await this._registerNewUser(user);
}
return userData;
}

private async _registerNewUser(user: Context["payload"]["sender"], locationMetaData: LocationMetaData) {
// Insert the location metadata into the locations table
const { data: locationData, error: locationError } = await this.supabase.from("locations").insert(locationMetaData).select().single();

if (locationError) {
throw new Error(locationError.message);
}

// Get the ID of the inserted location
const locationId = locationData.id;

// Register the new user with the location ID
private async _registerNewUser(user: Context["payload"]["sender"]) {
const { data: userData, error: userError } = await this.supabase
.from("users")
.insert([{ id: user.id, location_id: locationId }])
.insert([{ id: user.id }])
.select()
.single();

if (userError) {
throw new Error(userError.message);
throw this.context.logger.error(`A new user could not be registered.`, userError);
}

return userData as UserRow;
}

private async _checkIfWalletExists(userData: UserRow) {
if (userData.wallet_id === null) {
private async _checkIfWalletExists(wallet: string | number | null) {
if (wallet === null) {
return { data: null, error: null };
}
const { data, error } = await this.supabase.from("wallets").select("*").eq("id", userData.wallet_id).maybeSingle();

return { data, error };
if (typeof wallet === "number") {
return this.supabase.from("wallets").select("*").eq("id", wallet).maybeSingle();
} else {
return this.supabase.from("wallets").select("*").eq("address", wallet).maybeSingle();
}
}

private async _updateWalletId(walletId: number, userId: number) {
const { error } = await this.supabase.from("users").update({ wallet_id: walletId }).eq("id", userId);

if (error) {
throw error;
throw this.context.logger.error(`Could not update the wallet.`, error);
}
}

private async _getRegisteredWalletData(userData: UserRow) {
const walletResponse = await this._checkIfWalletExists(userData);
private async _getRegisteredWalletData(address: string) {
const walletResponse = await this._checkIfWalletExists(address);
const walletData = walletResponse.data;
const walletError = walletResponse.error;

if (walletError) throw walletError;
if (walletError) throw this.context.logger.error(`Could not get the registered wallet.`, walletError);
return walletData;
}

private _getLocationMetaData(payload: Context["payload"]): LocationMetaData {
return {
user_id: payload.sender.id,
comment_id: payload.comment.id,
issue_id: payload.issue.id,
repository_id: payload.repository.id,
organization_id: payload.organization?.id ?? payload.repository.owner.id,
};
}

private async _registerNewWallet(context: Context, { address, locationMetaData, payload }: RegisterNewWallet) {
private async _registerNewWallet(context: Context, { address, payload }: RegisterNewWallet) {
context.logger.debug(`Registering a new wallet for the user ${payload.sender.id}: ${address}`);
const walletData = await this._insertNewWallet(address);
await this._updateWalletId(walletData.id, payload.sender.id);
if (walletData.location_id) {
await this._enrichLocationMetaData(context, walletData, locationMetaData);
}
}

private async _updateExistingWallet(context: Context, { address, locationMetaData, walletData }: UpdateExistingWallet) {
await this._updateWalletAddress(walletData.id, address);
if (walletData.location_id) {
await this._enrichLocationMetaData(context, walletData, locationMetaData);
private async _updateExistingWallet(context: Context, { walletData, payload }: UpdateExistingWallet) {
context.logger.debug(`Updating a new wallet for the user ${payload.sender.id}: ${walletData.address}`);
const existingLinkToUserWallet = await this._getUserFromWalletId(walletData.id);
if (existingLinkToUserWallet && existingLinkToUserWallet.id !== context.payload.sender.id) {
throw this.context.logger.error(`Failed to register wallet because it is already associated with another user.`, existingLinkToUserWallet);
}
await this._updateWalletId(walletData.id, payload.sender.id);
}

private async _insertNewWallet(address: string): Promise<WalletRow> {
Expand All @@ -152,42 +149,16 @@ export class Wallet extends Super {

const { data: walletInsertData, error: walletInsertError } = await this.supabase.from("wallets").insert(newWallet).select().single();

if (walletInsertError) throw walletInsertError;
if (walletInsertError) throw this.context.logger.error(`Could not insert the new wallet.`, walletInsertError);
return walletInsertData as WalletRow;
}

private async _updateWalletAddress(walletId: number, address: string) {
const basicLocationInfo = {
address: address,
} as WalletRow;

await this.supabase.from("wallets").update(basicLocationInfo).eq("id", walletId).maybeSingle();
}

private async _enrichLocationMetaData(context: Context, walletData: WalletRow, locationMetaData: LocationMetaData) {
const logger = context.logger;
if (walletData.location_id === null) {
throw new Error("Location ID is null");
}
logger.debug("Enriching wallet location metadata", { locationMetaData });
return this.supabase.from("locations").update(locationMetaData).eq("id", walletData.location_id);
}
}

interface RegisterNewWallet {
address: string;
payload: Context["payload"];
locationMetaData: LocationMetaData;
}

interface UpdateExistingWallet extends RegisterNewWallet {
walletData: WalletRow;
}

interface LocationMetaData {
user_id: number;
comment_id: number;
issue_id: number;
repository_id: number;
organization_id: number;
}
16 changes: 13 additions & 3 deletions src/handlers/command-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Command, InvalidArgumentError } from "commander";
import packageJson from "../../package.json";
import { Context } from "../types";
import { registerWallet } from "./query-wallet";
import { registerWallet, unregisterWallet } from "./query-wallet";

export class CommandParser {
readonly _program;
Expand All @@ -11,8 +11,18 @@ export class CommandParser {
program
.command("/wallet")
.usage("<address>")
.argument("<address>", "Wallet address to query, e.g. 0x000000000000000000000000000000000000000", this._parseWalletAddress)
.action((address) => registerWallet(context, address))
.argument("[address]", "Wallet address to query, e.g. 0x000000000000000000000000000000000000000", this._parseWalletAddress)
.action((address: string) => {
if (address === "unset") {
return unregisterWallet(context);
} else if (address) {
return registerWallet(context, address);
} else {
throw new InvalidArgumentError(
`Please provide your wallet address after to register it i.e.: \`/wallet 0xYourAddress\`\nWrite \`/wallet unset\` to remove your wallet.`
);
}
})
.helpCommand(false)
.exitOverride()
.version(packageJson.version);
Expand Down
28 changes: 18 additions & 10 deletions src/handlers/query-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { postComment } from "@ubiquity-os/plugin-sdk";
import { ethers } from "ethers";
import { Context } from "../types";
import { RPCHandler } from "@ubiquity-dao/rpc-handler";
import { addCommentToIssue } from "../utils";

function extractEnsName(text: string) {
const ensRegex = /^(?=.{3,40}$)([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/gm;
Expand All @@ -18,8 +18,20 @@ export async function handleCommand(context: Context) {
if (!command) {
throw new Error("Command is undefined");
}
const { walletAddress } = command.parameters;
await registerWallet(context, walletAddress);
const { walletAddress, unset: shouldUnset } = command.parameters;
if (shouldUnset) {
await unregisterWallet(context);
} else {
await registerWallet(context, walletAddress);
}
}

export async function unregisterWallet(context: Context) {
const { payload, adapters, logger } = context;
const sender = payload.sender.id;
logger.info(`Trying to unlink the wallet for user ${sender}`);
await adapters.supabase.wallet.unlinkWalletFromUserId(sender);
await postComment(context, logger.ok(`Successfully unset wallet`));
}

export async function registerWallet(context: Context, body: string) {
Expand All @@ -40,7 +52,7 @@ export async function registerWallet(context: Context, body: string) {
}

if (!address) {
await addCommentToIssue(context, logger.info("Skipping to register a wallet address because both address/ens doesn't exist").logMessage.diff);
await postComment(context, logger.info("Skipping to register a wallet address because both address/ens doesn't exist"));
return;
}

Expand All @@ -49,11 +61,7 @@ export async function registerWallet(context: Context, body: string) {
}

if (address == ethers.ZeroAddress) {
await addCommentToIssue(
context,
logger.error("Skipping to register a wallet address because user is trying to set their address to null address").logMessage.diff
);

await postComment(context, logger.error("Skipping to register a wallet address because user is trying to set their address to null address"));
return;
}

Expand All @@ -63,7 +71,7 @@ export async function registerWallet(context: Context, body: string) {
const { wallet } = adapters.supabase;
await wallet.upsertWalletAddress(context, address);

await addCommentToIssue(context, logger.ok("Successfully registered wallet address", { sender, address }).logMessage.diff);
await postComment(context, logger.ok("Successfully set wallet", { sender, address }));
} else {
throw new Error("Payload comment is undefined");
}
Expand Down
5 changes: 1 addition & 4 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { CommanderError } from "commander";
import { createAdapters } from "./adapters";
import { CommandParser } from "./handlers/command-parser";
import { Context } from "./types";
import { addCommentToIssue } from "./utils";
import { handleCommand } from "./handlers/query-wallet";

/**
Expand All @@ -26,11 +25,9 @@ export async function plugin(context: Context) {
} catch (err) {
if (err instanceof CommanderError) {
if (err.code !== "commander.unknownCommand") {
await addCommentToIssue(context, `\`\`\`diff\n- ${err.message}`);
context.logger.error(err.message);
throw context.logger.error(err.message);
}
} else {
context.logger.error("An error occurred", { err });
throw err;
}
}
Expand Down
Loading

0 comments on commit bfd38c3

Please sign in to comment.