From 4926daf824c8fda2eb1225575a89867cd6643059 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 5 Nov 2023 17:22:20 +0100 Subject: [PATCH] fix: schema and decode --- src/bindings/event.ts | 15 +++-- .../issue/generate-permit-2-signature.ts | 1 + src/types/configuration-types.ts | 16 ++++-- src/utils/generate-configuration.ts | 57 ++++++------------- 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/src/bindings/event.ts b/src/bindings/event.ts index acf8e41a9..99b7d4caa 100644 --- a/src/bindings/event.ts +++ b/src/bindings/event.ts @@ -12,6 +12,8 @@ import Runtime from "./bot-runtime"; import { loadConfiguration } from "./config"; import { Context } from "../types/context"; +const allowedEvents = Object.values(GitHubEvent) as string[]; + const NO_VALIDATION = [GitHubEvent.INSTALLATION_ADDED_EVENT, GitHubEvent.PUSH_EVENT] as string[]; type PreHandlerWithType = { type: string; actions: PreActionHandler[] }; type HandlerWithType = { type: string; actions: MainActionHandler[] }; @@ -20,6 +22,8 @@ type PostHandlerWithType = { type: string; actions: PostActionHandler[] }; type AllHandlersWithTypes = PreHandlerWithType | HandlerWithType | PostHandlerWithType; type AllHandlers = PreActionHandler | MainActionHandler | PostActionHandler; +const validatePayload = ajv.compile(PayloadSchema); + export async function bindEvents(eventContext: ProbotContext) { const runtime = Runtime.getState(); @@ -37,8 +41,8 @@ export async function bindEvents(eventContext: ProbotContext) { } const payload = eventContext.payload as Payload; - const allowedEvents = Object.values(GitHubEvent) as string[]; const eventName = payload?.action ? `${eventContext.name}.${payload?.action}` : eventContext.name; // some events wont have actions as this grows + if (eventName === GitHubEvent.PUSH_EVENT) { await validateConfigChange(context); } @@ -47,7 +51,7 @@ export async function bindEvents(eventContext: ProbotContext) { throw new Error("Failed to create logger"); } - runtime.logger.info("Binding events", { id: eventContext.id, name: eventName, allowedEvents }); + runtime.logger.info("Event received", { id: eventContext.id, name: eventName }); if (!allowedEvents.includes(eventName)) { // just check if its on the watch list @@ -58,12 +62,11 @@ export async function bindEvents(eventContext: ProbotContext) { if (!NO_VALIDATION.includes(eventName)) { // Validate payload // console.trace({ payload }); - const validate = ajv.compile(PayloadSchema); - const valid = validate(payload); + const valid = validatePayload(payload); if (!valid) { // runtime.logger.info("Payload schema validation failed!", payload); - if (validate.errors) { - return runtime.logger.error("validation errors", validate.errors); + if (validatePayload.errors) { + return runtime.logger.error("validation errors", validatePayload.errors); } // return; } diff --git a/src/handlers/comment/handlers/issue/generate-permit-2-signature.ts b/src/handlers/comment/handlers/issue/generate-permit-2-signature.ts index d9e76e65a..abf76184b 100644 --- a/src/handlers/comment/handlers/issue/generate-permit-2-signature.ts +++ b/src/handlers/comment/handlers/issue/generate-permit-2-signature.ts @@ -16,6 +16,7 @@ export async function generatePermit2Signature( payments: { evmNetworkId }, keys: { evmPrivateEncrypted }, } = context.config; + if (!evmPrivateEncrypted) throw runtime.logger.warn("No bot wallet private key defined"); const { rpc, paymentToken } = getPayoutConfigByNetworkId(evmNetworkId); const { privateKey } = await decryptKeys(evmPrivateEncrypted); diff --git a/src/types/configuration-types.ts b/src/types/configuration-types.ts index b62bea022..615fefa61 100644 --- a/src/types/configuration-types.ts +++ b/src/types/configuration-types.ts @@ -1,4 +1,4 @@ -import { Type as T, Static, TProperties, TObject, ObjectOptions, StringOptions, StaticDecode } from "@sinclair/typebox"; +import { Type as T, Static, TProperties, ObjectOptions, StringOptions, StaticDecode } from "@sinclair/typebox"; import { LogLevel } from "../types"; import { validHTMLElements } from "../handlers/comment/handlers/issue/valid-html-elements"; import { userCommands } from "../handlers"; @@ -35,13 +35,17 @@ const defaultPriorityLabels = [ { name: "Priority: 5 (Emergency)" }, ]; -function StrictObject(obj: T, options?: ObjectOptions): TObject { +function StrictObject(obj: T, options?: ObjectOptions) { return T.Object(obj, { additionalProperties: false, default: {}, ...options }); } function stringDuration(options?: StringOptions) { return T.Transform(T.String(options)) .Decode((value) => { + const decoded = ms(value); + if (decoded === undefined || isNaN(decoded)) { + throw new Error(`Invalid duration string: ${value}`); + } return ms(value); }) .Encode((value) => { @@ -52,7 +56,7 @@ function stringDuration(options?: StringOptions) { export const EnvConfigSchema = T.Object({ WEBHOOK_PROXY_URL: T.String({ format: "uri" }), LOG_ENVIRONMENT: T.String({ default: "production" }), - LOG_LEVEL: T.Enum(LogLevel), + LOG_LEVEL: T.Enum(LogLevel, { default: LogLevel.SILLY }), LOG_RETRY_LIMIT: T.Number({ default: 8 }), SUPABASE_URL: T.String({ format: "uri" }), SUPABASE_KEY: T.String(), @@ -67,7 +71,7 @@ export type EnvConfig = Static; export const BotConfigSchema = StrictObject( { keys: StrictObject({ - evmPrivateEncrypted: T.String(), + evmPrivateEncrypted: T.Optional(T.String()), openAi: T.Optional(T.String()), }), features: StrictObject({ @@ -89,7 +93,7 @@ export const BotConfigSchema = StrictObject( }), timers: StrictObject({ reviewDelayTolerance: stringDuration({ default: "1 day" }), - taskStaleTimeoutDuration: stringDuration({ default: "1 month" }), + taskStaleTimeoutDuration: stringDuration({ default: "4 weeks" }), taskFollowUpDuration: stringDuration({ default: "0.5 weeks" }), taskDisqualifyDuration: stringDuration({ default: "1 week" }), }), @@ -108,7 +112,7 @@ export const BotConfigSchema = StrictObject( ), incentives: StrictObject({ comment: StrictObject({ - elements: T.Record(T.Union(HtmlEntities), T.Number(), { default: allHtmlElementsSetToZero }), + elements: T.Record(T.Union(HtmlEntities), T.Number({ default: 0 }), { default: allHtmlElementsSetToZero }), totals: StrictObject({ character: T.Number({ default: 0, minimum: 0 }), word: T.Number({ default: 0, minimum: 0 }), diff --git a/src/utils/generate-configuration.ts b/src/utils/generate-configuration.ts index d997a0c3d..ae8e508ec 100644 --- a/src/utils/generate-configuration.ts +++ b/src/utils/generate-configuration.ts @@ -1,5 +1,5 @@ import merge from "lodash/merge"; -import { Context } from "probot"; +import { Context as ProbotContext } from "probot"; import YAML from "yaml"; import Runtime from "../bindings/bot-runtime"; import { Payload, BotConfig, validateBotConfig, BotConfigSchema } from "../types"; @@ -9,10 +9,10 @@ import { Value } from "@sinclair/typebox/value"; const UBIQUIBOT_CONFIG_REPOSITORY = "ubiquibot-config"; const UBIQUIBOT_CONFIG_FULL_PATH = ".github/ubiquibot-config.yml"; -export async function generateConfiguration(context: Context): Promise { +export async function generateConfiguration(context: ProbotContext): Promise { const payload = context.payload as Payload; - let organizationConfiguration = parseYaml( + const organizationConfiguration = parseYaml( await download({ context, repository: UBIQUIBOT_CONFIG_REPOSITORY, @@ -20,7 +20,7 @@ export async function generateConfiguration(context: Context): Promise !(error.keyword === "required" && error.params.missingProperty === "evmPrivateEncrypted") - ); - const err = generateValidationError(errors as DefinedError[]); + const err = generateValidationError(validateBotConfig.errors as DefinedError[]); if (err instanceof Error) throw err; if (payload.issue?.number) await context.octokit.issues.createComment({ @@ -52,14 +47,9 @@ export async function generateConfiguration(context: Context): Promise !(error.keyword === "required" && error.params.missingProperty === "evmPrivateEncrypted") - ); - const err = generateValidationError(errors as DefinedError[]); + const err = generateValidationError(validateBotConfig.errors as DefinedError[]); if (err instanceof Error) throw err; if (payload.issue?.number) await context.octokit.issues.createComment({ @@ -72,7 +62,7 @@ export async function generateConfiguration(context: Context): Promise {