Skip to content

Commit

Permalink
feat: new config
Browse files Browse the repository at this point in the history
  • Loading branch information
whilefoo committed Nov 3, 2023
1 parent f5da0ab commit 85a3bf4
Show file tree
Hide file tree
Showing 39 changed files with 303 additions and 217 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@octokit/rest": "^20.0.2",
"@openzeppelin/contracts": "^5.0.0",
"@probot/adapter-github-actions": "^3.1.3",
"@sinclair/typebox": "^0.31.5",
"@sinclair/typebox": "^0.31.22",
"@supabase/supabase-js": "^2.4.0",
"@types/ms": "^0.7.31",
"@types/parse5": "^7.0.0",
Expand Down
27 changes: 12 additions & 15 deletions src/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,22 @@ import { Super } from "./supabase/helpers/tables/super";
import { User } from "./supabase/helpers/tables/user";
import { Wallet } from "./supabase/helpers/tables/wallet";
import { Database } from "./supabase/types";
import { env } from "../bindings/env";

const supabaseClient = createClient<Database>(env.SUPABASE_URL, env.SUPABASE_KEY, { auth: { persistSession: false } });

export function createAdapters(context: Context) {
const client = generateSupabase(context.config.supabase.url, context.config.supabase.key);
return {
supabase: {
access: new Access(client, context),
wallet: new Wallet(client, context),
user: new User(client, context),
debit: new Settlement(client, context),
settlement: new Settlement(client, context),
label: new Label(client, context),
logs: new Logs(client, context),
locations: new Locations(client, context),
super: new Super(client, context),
access: new Access(supabaseClient, context),
wallet: new Wallet(supabaseClient, context),
user: new User(supabaseClient, context),
debit: new Settlement(supabaseClient, context),
settlement: new Settlement(supabaseClient, context),
label: new Label(supabaseClient, context),
logs: new Logs(supabaseClient, context),
locations: new Locations(supabaseClient, context),
super: new Super(supabaseClient, context),
},
};
}

function generateSupabase(url?: string | null, key?: string | null) {
if (!url || !key) throw new Error("Supabase URL or key is not defined");
return createClient<Database>(url, key, { auth: { persistSession: false } });
}
20 changes: 5 additions & 15 deletions src/adapters/supabase/helpers/tables/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { Database } from "../../types";
import { prettyLogs } from "../pretty-logs";
import { Super } from "./super";
import { execSync } from "child_process";
import { Context } from "../../../../types";
import { Context, LogLevel } from "../../../../types";
import Runtime from "../../../../bindings/bot-runtime";
import { env } from "../../../../bindings/env";

type LogFunction = (message: string, metadata?: any) => void;
type LogInsert = Database["public"]["Tables"]["logs"]["Insert"];
Expand Down Expand Up @@ -224,11 +225,10 @@ export class Logs extends Super {

constructor(supabase: SupabaseClient, context: Context) {
super(supabase, context);
const logConfig = this.context.config.log;

this.environment = logConfig.logEnvironment;
this.retryLimit = logConfig.retryLimit;
this.maxLevel = this._getNumericLevel(logConfig.level ?? LogLevel.DEBUG);
this.environment = env.LOG_ENVIRONMENT;
this.retryLimit = env.LOG_RETRY_LIMIT;
this.maxLevel = this._getNumericLevel(env.LOG_LEVEL);
}

private async _sendLogsToSupabase(log: LogInsert) {
Expand Down Expand Up @@ -411,13 +411,3 @@ export class Logs extends Super {
return obj;
}
}

export enum LogLevel {
ERROR = "error",
WARN = "warn",
INFO = "info",
HTTP = "http",
VERBOSE = "verbose",
DEBUG = "debug",
SILLY = "silly",
}
4 changes: 2 additions & 2 deletions src/adapters/supabase/helpers/tables/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ dotenv.config();

import { Context as ProbotContext } from "probot";
import { createAdapters } from "../../..";
import { loadConfig } from "../../../../bindings/config";
import { loadConfiguration } from "../../../../bindings/config";
import { Context, User } from "../../../../types";
const SUPABASE_URL = process.env.SUPABASE_URL;
if (!SUPABASE_URL) throw new Error("SUPABASE_URL is not defined");
Expand All @@ -13,7 +13,7 @@ if (!SUPABASE_KEY) throw new Error("SUPABASE_KEY is not defined");
const mockContext = { supabase: { url: SUPABASE_URL, key: SUPABASE_KEY } } as unknown as ProbotContext;

async function getWalletAddressAndUrlTest(eventContext: ProbotContext) {
const botConfig = await loadConfig(eventContext);
const botConfig = await loadConfiguration(eventContext);
const context: Context = { event: eventContext, config: botConfig };
const { wallet } = createAdapters(context).supabase;
const userId = 4975670 as User["id"];
Expand Down
22 changes: 3 additions & 19 deletions src/bindings/config.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import ms from "ms";
import { getPayoutConfigByNetworkId } from "../helpers";
import { ajv, validateTypes } from "../utils";
import { Context } from "probot";
import { Context as ProbotContext } from "probot";
import { generateConfiguration } from "../utils/generate-configuration";
import { LogLevel } from "../adapters/supabase/helpers/tables/logs";
import Runtime from "./bot-runtime";
import {
AllConfigurationTypes,
PublicConfigurationTypes,
PublicConfigurationValues,
} from "../types/configuration-types";
import defaultConfiguration from "../ubiquibot-config-default";
import { BotConfig } from "../types/configuration-types";

const defaultConfigurationValidation = validateTypes(PublicConfigurationValues, defaultConfiguration);

if (defaultConfigurationValidation.error) {
throw new Error(defaultConfigurationValidation.error);
}

export async function loadConfiguration(context: Context): Promise<AllConfigurationTypes> {
export async function loadConfiguration(context: ProbotContext): Promise<BotConfig> {
// const runtime = Runtime.getState();
const configuration = await generateConfiguration(context);
console.trace({ configuration });
Expand Down
10 changes: 10 additions & 0 deletions src/bindings/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { EnvConfig, validateEnvConfig } from "../types";
import dotenv from "dotenv";
dotenv.config();

export const env = { ...process.env } as unknown as EnvConfig;

const valid = validateEnvConfig(env);
if (!valid) {
throw new Error("Invalid env configuration: " + JSON.stringify(validateEnvConfig.errors, null, 2));
}
6 changes: 3 additions & 3 deletions src/bindings/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { Payload } from "../types/payload";
import { ajv } from "../utils";
import Runtime from "./bot-runtime";
import { loadConfig } from "./config";
import { loadConfiguration } from "./config";
import { Context } from "../types";

const NO_VALIDATION = [GitHubEvent.INSTALLATION_ADDED_EVENT, GitHubEvent.PUSH_EVENT] as string[];
Expand All @@ -30,7 +30,7 @@ type AllHandlers = PreActionHandler | MainActionHandler | PostActionHandler;
export async function bindEvents(eventContext: ProbotContext) {
const runtime = Runtime.getState();

const botConfig = await loadConfig(eventContext);
const botConfig = await loadConfiguration(eventContext);
const context: Context = {
event: eventContext,
config: botConfig,
Expand All @@ -39,7 +39,7 @@ export async function bindEvents(eventContext: ProbotContext) {
runtime.adapters = createAdapters(context);
runtime.logger = runtime.adapters.supabase.logs;

if (!context.config.payout.privateKey) {
if (!context.config.keys.evmPrivateEncrypted) {
runtime.logger.warn("No EVM private key found");
}

Expand Down
4 changes: 3 additions & 1 deletion src/handlers/access/labels-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Context, Payload, UserType } from "../../types";
export async function labelAccessPermissionsCheck(context: Context) {
const runtime = Runtime.getState();
const logger = runtime.logger;
const { publicAccessControl } = context.config;
const {
features: { publicAccessControl },
} = context.config;
if (!publicAccessControl.setLabel) return true;

const payload = context.event.payload as Payload;
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/assign/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function startCommandHandler(context: Context) {
// Filter out labels that match the time labels defined in the config
const timeLabelsAssigned: Label[] = labels.filter((label) =>
typeof label === "string" || typeof label === "object"
? config.price.timeLabels.some((item) => item.name === label.name)
? config.labels.time.some((item) => item.name === label.name)
: false
);

Expand Down
6 changes: 3 additions & 3 deletions src/handlers/comment/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function commentCreatedOrEdited(context: Context) {
const comment = payload.comment as Comment;

const body = comment.body;
const commentedCommand = commentParser(context, body);
const commentedCommand = commentParser(body);

if (!comment) {
logger.info(`Comment is null. Skipping`);
Expand All @@ -26,14 +26,14 @@ export async function commentCreatedOrEdited(context: Context) {
await verifyFirstCommentInRepository(context);
}

const allCommands = userCommands(context);
const allCommands = userCommands(config.miscellaneous.registerWalletWithVerification);
const userCommand = allCommands.find((i) => i.id == commentedCommand);

if (userCommand) {
const { id, handler } = userCommand;
logger.info("Running a comment handler", { id, handler: handler.name });

const feature = config.command.find((e) => e.name === id.split("/")[1]);
const feature = config.commands.find((e) => e.name === id.split("/")[1]);

if (feature?.enabled === false && id !== "/help") {
return logger.warn("Skipping because it is disabled on this repo.", { id });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function getTimeLabelsAssigned(payload: Payload, config: BotConfig) {
logger.warn("Skipping '/start' since no labels are set to calculate the timeline", { labels });
return;
}
const timeLabelsDefined = config.price.timeLabels;
const timeLabelsDefined = config.labels.time;
const timeLabelsAssigned: Label[] = [];
for (const _label of labels) {
const _labelType = typeof _label;
Expand Down
14 changes: 9 additions & 5 deletions src/handlers/comment/handlers/assign/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ export async function assign(context: Context, body: string) {
const config = context.config;
const payload = context.event.payload as Payload;
const issue = payload.issue;
const {
miscellaneous: { maxConcurrentTasks },
timers: { taskStaleTimeoutDuration },
commands,
} = context.config;

const taskStaleTimeoutDuration = config.assign.taskStaleTimeoutDuration;
const startEnabled = config.command.find((command) => command.name === "start");
const startEnabled = commands.find((command) => command.name === "start");

logger.info("Received '/start' command", { sender: payload.sender.login, body });

Expand All @@ -47,12 +51,12 @@ export async function assign(context: Context, body: string) {
);

const assignedIssues = await getAssignedIssues(context, payload.sender.login);
logger.info("Max issue allowed is", config.assign.maxConcurrentTasks);
logger.info("Max issue allowed is", maxConcurrentTasks);

// check for max and enforce max
if (assignedIssues.length - openedPullRequests.length >= config.assign.maxConcurrentTasks) {
if (assignedIssues.length - openedPullRequests.length >= maxConcurrentTasks) {
throw logger.warn("Too many assigned issues, you have reached your max limit", {
maxConcurrentTasks: config.assign.maxConcurrentTasks,
maxConcurrentTasks,
});
}

Expand Down
4 changes: 3 additions & 1 deletion src/handlers/comment/handlers/first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export async function verifyFirstCommentInRepository(context: Context) {
throw runtime.logger.error("Issue is null. Skipping", { issue: payload.issue }, true);
}
const {
newContributorGreeting: { header, footer, enabled },
features: {
newContributorGreeting: { header, footer, enabled },
},
} = context.config;
const response_issue = await context.event.octokit.rest.search.issuesAndPullRequests({
q: `is:issue repo:${payload.repository.owner.login}/${payload.repository.name} commenter:${payload.sender.login}`,
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/comment/handlers/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export async function listAvailableCommands(context: Context, body: string) {

export function generateHelpMenu(context: Context) {
const config = context.config;
const startEnabled = config.command.find((command) => command.name === "start");
const startEnabled = config.commands.find((command) => command.name === "start");
let helpMenu = "### Available Commands\n\n| Command | Description | Example |\n| --- | --- | --- |\n";
const commands = userCommands(context);
const commands = userCommands(config.miscellaneous.registerWalletWithVerification);

commands.map(
(command) =>
Expand Down
13 changes: 6 additions & 7 deletions src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, UserCommands } from "../../../types";
import { UserCommands } from "../../../types";
import { assign } from "./assign";
import { listAvailableCommands } from "./help";
// Commented out until Gnosis Safe is integrated (https://github.com/ubiquity/ubiquibot/issues/353)
Expand All @@ -25,8 +25,8 @@ export * from "./unassign";
export * from "./wallet";

// Parses the comment body and figure out the command name a user wants
export function commentParser(context: Context, body: string): null | string {
const userCommandIds = userCommands(context).map((cmd) => cmd.id);
export function commentParser(body: string): null | string {
const userCommandIds = userCommands(false).map((cmd) => cmd.id);
const regex = new RegExp(`^(${userCommandIds.join("|")})\\b`); // Regex pattern to match any command at the beginning of the body

const matches = regex.exec(body);
Expand All @@ -40,8 +40,8 @@ export function commentParser(context: Context, body: string): null | string {
return null;
}

export function userCommands(context: Context): UserCommands[] {
const accountForWalletVerification = walletVerificationDetails(context);
export function userCommands(walletVerificationEnabled: boolean): UserCommands[] {
const accountForWalletVerification = walletVerificationDetails(walletVerificationEnabled);
return [
{
id: "/start",
Expand Down Expand Up @@ -112,7 +112,7 @@ export function userCommands(context: Context): UserCommands[] {
];
}

function walletVerificationDetails(context: Context) {
function walletVerificationDetails(walletVerificationEnabled: boolean) {
const base = {
description: "Register your wallet address for payments.",
example: "/wallet ubq.eth",
Expand All @@ -125,7 +125,6 @@ function walletVerificationDetails(context: Context) {
"0xe2a3e34a63f3def2c29605de82225b79e1398190b542be917ef88a8e93ff9dc91bdc3ef9b12ed711550f6d2cbbb50671aa3f14a665b709ec391f3e603d0899a41b",
};

const walletVerificationEnabled = context.config.wallet.registerWalletWithVerification;
if (walletVerificationEnabled) {
return {
description: `${base.description} ${withVerification.description}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { encodingForModel } from "js-tiktoken";
import Decimal from "decimal.js";
import Runtime from "../../../../bindings/bot-runtime";

const openai = new OpenAI(); // apiKey: // defaults to process.env["OPENAI_API_KEY"]
//const openai = new OpenAI(); // apiKey: // defaults to process.env["OPENAI_API_KEY"]

export async function calculateQualScore(issue: Issue, contributorComments: Comment[]) {
const sumOfConversationTokens = countTokensOfConversation(issue, contributorComments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ import { BigNumber, ethers } from "ethers";
import { keccak256, toUtf8Bytes } from "ethers/lib/utils";
import Runtime from "../../../../bindings/bot-runtime";
import { Context } from "../../../../types";
import { getPayoutConfigByNetworkId } from "../../../../helpers";
import { decryptKeys } from "../../../../utils/private";

export async function generatePermit2Signature(
context: Context,
{ beneficiary, amount, identifier, userId }: GeneratePermit2SignatureParams
) {
const runtime = Runtime.getState();
const {
payout: { privateKey, paymentToken, rpc, evmNetworkId },
payments: { evmNetworkId },
keys: { evmPrivateEncrypted },
} = context.config;
const { rpc, paymentToken } = getPayoutConfigByNetworkId(evmNetworkId);
const { privateKey } = await decryptKeys(evmPrivateEncrypted);

if (!rpc) throw runtime.logger.error("RPC is not defined");
if (!privateKey) throw runtime.logger.error("Private key is not defined");
Expand Down
7 changes: 5 additions & 2 deletions src/handlers/comment/handlers/issue/generate-permits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import structuredMetadata from "../../../shared/structured-metadata";
import { generatePermit2Signature } from "./generate-permit-2-signature";
import { FinalScores } from "./issue-closed";
import { IssueRole } from "./_calculate-all-comment-scores";
import { getPayoutConfigByNetworkId } from "../../../../helpers";

export async function generatePermits(context: Context, totals: FinalScores, contributorComments: Comment[]) {
const userIdToNameMap = mapIdsToNames(contributorComments);
Expand All @@ -23,8 +24,10 @@ async function generateComment(
) {
const runtime = Runtime.getState();
const {
payout: { paymentToken, rpc, privateKey },
payments: { evmNetworkId },
keys: { evmPrivateEncrypted },
} = context.config;
const { rpc, paymentToken } = getPayoutConfigByNetworkId(evmNetworkId);
const detailsTable = generateDetailsTable(totals, contributorComments);
const tokenSymbol = await getTokenSymbol(paymentToken, rpc);
const HTMLs = [] as string[];
Expand All @@ -38,7 +41,7 @@ async function generateComment(
const contributorName = userIdToNameMap[userId];
const issueRole = userTotals.role;

if (!privateKey) throw runtime.logger.warn("No bot wallet private key defined");
if (!evmPrivateEncrypted) throw runtime.logger.warn("No bot wallet private key defined");

const beneficiaryAddress = await runtime.adapters.supabase.wallet.getAddress(parseInt(userId));

Expand Down
2 changes: 1 addition & 1 deletion src/handlers/comment/handlers/issue/issue-closed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function preflightChecks(context: Context, issue: Issue, logger: Logs, iss
if (!issue) throw logger.error("Permit generation skipped because issue is undefined");
if (issue.state_reason !== StateReason.COMPLETED)
throw logger.info("Issue was not closed as completed. Skipping.", { issue });
if (config.publicAccessControl.fundExternalClosedIssue) {
if (config.features.publicAccessControl.fundExternalClosedIssue) {
const userHasPermission = await checkUserPermissionForRepoAndOrg(context, payload.sender.login);
if (!userHasPermission)
throw logger.warn("Permit generation disabled because this issue has been closed by an external contributor.");
Expand Down
Loading

0 comments on commit 85a3bf4

Please sign in to comment.