From 0e57e60f3f803e0bf05b498678799b26f6906c41 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Fri, 17 Oct 2025 01:50:12 -0400 Subject: [PATCH] latest --- Makefile | 3 + src/lib/data/standards.json | 6 +- src/lib/server/app.ts | 57 +++++- src/lib/services/calculator/main.ts | 45 +++-- src/lib/services/commerce/printful.ts | 25 ++- src/lib/services/commerce/util/misc.ts | 194 +++++++++++++++++- src/lib/services/commerce/util/types.ts | 254 +++++++++++++++++++++--- src/lib/services/commerce/webflow.ts | 62 +++++- src/routes/calculator/+page.svelte | 70 +++++-- 9 files changed, 635 insertions(+), 81 deletions(-) diff --git a/Makefile b/Makefile index 3146b18..acbfd3b 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,5 @@ deploy-secrets: fly secrets import < .env + +deploy: + bun vite build && fly deploy diff --git a/src/lib/data/standards.json b/src/lib/data/standards.json index c01800e..4d2dc9f 100644 --- a/src/lib/data/standards.json +++ b/src/lib/data/standards.json @@ -4741,7 +4741,7 @@ "metric": "weight", "spread": 3, "step": 12, - "ratio": "inverse" + "ratio": "normal" } ], "unit": "ms", @@ -5285,13 +5285,13 @@ "metric": "weight", "spread": 3, "step": 12, - "ratio": "inverse" + "ratio": "normal" }, { "metric": "age", "spread": 4, "step": 10, - "ratio": "inverse" + "ratio": "normal" } ], "unit": "ms", diff --git a/src/lib/server/app.ts b/src/lib/server/app.ts index 0debdd3..3c59fc1 100644 --- a/src/lib/server/app.ts +++ b/src/lib/server/app.ts @@ -3,11 +3,11 @@ import { cors } from "@elysiajs/cors"; import { LevelCalculator, Standards, type ActivityStandards } from "$lib/services/calculator/main"; import type { ActivityPerformance, Player } from "$lib/services/calculator/util"; import rawStandards from "$lib/data/standards.json" assert { type: "json" } -import { Printful } from "../services/commerce/util/types"; +import { Printful, Webflow } from "../services/commerce/util/types"; import WebflowService from "../services/commerce/webflow"; import SyncService from "../services/commerce/sync"; import PrintfulService from "../services/commerce/printful"; -import { FetchError } from "../services/commerce/util/misc"; +import { FetchError, getStateFromZip } from "../services/commerce/util/misc"; export const app = new Elysia({ prefix: "/api" }) .use(cors({ origin: '*' })) @@ -16,10 +16,10 @@ export const app = new Elysia({ prefix: "/api" }) FetchError }) - .onError(({ code, error }) => { + .onError(async ({ code, error }) => { switch (code) { case "FetchError": - console.error(error.message, error.payload); + console.error(error.message, await error.parse()); return error; } }) @@ -75,6 +75,55 @@ export const app = new Elysia({ prefix: "/api" }) } case Printful.Webhook.Event.ProductDeleted: { await WebflowService.Products.remove(payload.data.sync_product.external_id); + break; + } + } + }) + .post("/webhook/webflow", async ({ request, body, set }) => { + if (!WebflowService.Util.verifyWebflowSignature(request, body)) { + set.status = 400; + return "Invalid signature"; + } + + const payload = body as Webflow.Webhook.EventPayload; + + console.log(payload.triggerType); + switch (payload.triggerType) { + case Webflow.Webhook.Event.OrderCreated: { + const webflowOrder = payload.payload; + + console.log("Creating printful order..."); + await PrintfulService.Orders.create({ + external_id: webflowOrder.orderId, + // TODO: derive from webflow + shipping: "STANDARD", + recipient: { + name: webflowOrder.shippingAddress.addressee, + address1: webflowOrder.shippingAddress.line1, + address2: webflowOrder.shippingAddress.line2, + city: webflowOrder.shippingAddress.city, + state_code: getStateFromZip(webflowOrder.shippingAddress.postalCode), + country_code: webflowOrder.shippingAddress.country, + zip: webflowOrder.shippingAddress.postalCode + }, + items: webflowOrder.purchasedItems.map(webflowOrderSku => ({ + external_variant_id: webflowOrderSku.variantId, + quantity: webflowOrderSku.count + })) + }); + + // set webflow order to pending + + console.log("Complete"); + + break; + } + case Webflow.Webhook.Event.OrderUpdated: { + + // read printful order status + + // set webflow order to fulfulled (if indeed printful order got confirmed) + break; } } diff --git a/src/lib/services/calculator/main.ts b/src/lib/services/calculator/main.ts index 22dcfa4..1c62002 100644 --- a/src/lib/services/calculator/main.ts +++ b/src/lib/services/calculator/main.ts @@ -178,11 +178,15 @@ export class LevelCalculator { } export type StandardsConfig = { - maxLevel: number, - weightModifier: number, - weightSkew: number, - ageModifier: number, - disableGeneration: boolean + global: { + maxLevel: number, + }, + activity: Record } export class Standards { @@ -190,12 +194,23 @@ export class Standards { private activityStandards: ActivityStandards; constructor(activityStandards: ActivityStandards, cfg?: Partial) { + const defaultActivitiesConfig = Object.fromEntries( + Object.values(Activity).map(activity => [ + activity, + { + disableGeneration: false, + weightModifier: 0.1, + weightSkew: 0, + ageModifier: 0.1, + }, + ]) + ) as Record; + this.cfg = { - maxLevel: cfg?.maxLevel ?? 100, - weightModifier: cfg?.weightModifier ?? .1, - weightSkew: cfg?.weightSkew ?? .1, - ageModifier: cfg?.ageModifier ?? .1, - disableGeneration: cfg?.disableGeneration ?? false + global: { + maxLevel: cfg?.global?.maxLevel ?? 100, + }, + activity: Object.assign(defaultActivitiesConfig, cfg?.activity) }; // prepare data @@ -204,12 +219,12 @@ export class Standards { // expand/compress for (const standard of this.activityStandards[activity].standards) { const expandedLevels = this.expandLevels(standard.levels, 5); - const compressedLevels = this.compressLevels(expandedLevels, this.cfg.maxLevel); + const compressedLevels = this.compressLevels(expandedLevels, this.cfg.global.maxLevel); standard.levels = compressedLevels; } // generate data - const allGenerators = this.cfg.disableGeneration ? + const allGenerators = this.cfg.activity[activity].disableGeneration ? [] : this.activityStandards[activity].metadata.generators; @@ -241,7 +256,7 @@ export class Standards { for (const lvl in referenceStandard.levels) { if (!referenceStandard.levels[lvl]) continue; - const scalingExponent = this.cfg.ageModifier; + const scalingExponent = this.cfg.activity[activity].ageModifier; const coefficient = ageGenerator.ratio === "inverse" ? referenceAge / currAge : currAge / referenceAge; @@ -271,7 +286,7 @@ export class Standards { for (const age of this.agesFor(activity, gender)) { const referenceWeight = getAvgWeight(gender, age); - const skewRatio = this.cfg.weightSkew; // 0 = normal, 1 = max skew + const skewRatio = this.cfg.activity[activity].weightSkew; // 0 = normal, 1 = max skew const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread * (1 - skewRatio)); const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread * (1 + skewRatio)); @@ -295,7 +310,7 @@ export class Standards { for (const lvl in referenceStandard.levels) { if (!referenceStandard.levels[lvl]) continue; - const scalingExponent = this.cfg.weightModifier; + const scalingExponent = this.cfg.activity[activity].weightModifier; const coefficient = weightGenerator.ratio === "inverse" ? referenceWeight / currWeight : currWeight / referenceWeight; diff --git a/src/lib/services/commerce/printful.ts b/src/lib/services/commerce/printful.ts index 1865e25..723c081 100644 --- a/src/lib/services/commerce/printful.ts +++ b/src/lib/services/commerce/printful.ts @@ -15,7 +15,7 @@ export default class PrintfulService { } if (!res.ok) { - throw await FetchError.createAndParse("Failed to get Printful variant", res); + throw new FetchError("Failed to get Printful variant", res); } const payload: Printful.Products.MetaDataSingle = await res.json(); @@ -34,7 +34,7 @@ export default class PrintfulService { }) if (!res.ok) { - throw await FetchError.createAndParse("Failed to get all Printful products", res); + throw new FetchError("Failed to get all Printful products", res); } const payload: Printful.Products.MetaDataMulti = await res.json(); @@ -63,7 +63,7 @@ export default class PrintfulService { } if (!res.ok) { - throw await FetchError.createAndParse("Failed to get Printful product", res); + throw new FetchError("Failed to get Printful product", res); } const payload: Printful.Products.MetaDataSingle = await res.json(); @@ -81,7 +81,24 @@ export default class PrintfulService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to update Printful product", res); + throw new FetchError("Failed to update Printful product", res); + } + } + } + + static Orders = class { + static async create(printfulOrder: Printful.Orders.Order) { + const res = await fetch(`${env().API_URL}/orders`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...env().AUTH_HEADERS + }, + body: JSON.stringify(printfulOrder) + }); + + if (!res.ok) { + throw new FetchError("Failed to create printful order", res); } } } diff --git a/src/lib/services/commerce/util/misc.ts b/src/lib/services/commerce/util/misc.ts index 50d16d5..05c165e 100644 --- a/src/lib/services/commerce/util/misc.ts +++ b/src/lib/services/commerce/util/misc.ts @@ -20,16 +20,200 @@ export class FetchError extends Error { this.name = "FetchError"; } - static async createAndParse(message: string, response: Response): Promise { - const fetchError = new FetchError(message, response); + async parse() { try { - fetchError.payload = await fetchError.response.clone().json(); + this.payload = await this.response.clone().json(); } catch { - fetchError.payload = await fetchError.response.text().catch(() => undefined); + this.payload = await this.response.text().catch(() => undefined); } finally { - return fetchError; + return this.payload; } } } +export function getStateFromZip(zipString: string): string { + + /* Ensure param is a string to prevent unpredictable parsing results */ + if (typeof zipString !== 'string') { + console.error('Must pass the zipcode as a string.'); + return "none"; + } + + /* Ensure we have exactly 5 characters to parse */ + if (zipString.length !== 5) { + console.error('Must pass a 5-digit zipcode.'); + return "none"; + } + + /* Ensure we don't parse strings starting with 0 as octal values */ + const zipcode = parseInt(zipString, 10); + + let st; + let state; + + /* Code cases alphabetized by state */ + if (zipcode >= 35000 && zipcode <= 36999) { + st = 'AL'; + state = 'Alabama'; + } else if (zipcode >= 99500 && zipcode <= 99999) { + st = 'AK'; + state = 'Alaska'; + } else if (zipcode >= 85000 && zipcode <= 86999) { + st = 'AZ'; + state = 'Arizona'; + } else if (zipcode >= 71600 && zipcode <= 72999) { + st = 'AR'; + state = 'Arkansas'; + } else if (zipcode >= 90000 && zipcode <= 96699) { + st = 'CA'; + state = 'California'; + } else if (zipcode >= 80000 && zipcode <= 81999) { + st = 'CO'; + state = 'Colorado'; + } else if ((zipcode >= 6000 && zipcode <= 6389) || (zipcode >= 6391 && zipcode <= 6999)) { + st = 'CT'; + state = 'Connecticut'; + } else if (zipcode >= 19700 && zipcode <= 19999) { + st = 'DE'; + state = 'Delaware'; + } else if (zipcode >= 32000 && zipcode <= 34999) { + st = 'FL'; + state = 'Florida'; + } else if ((zipcode >= 30000 && zipcode <= 31999) || (zipcode >= 39800 && zipcode <= 39999)) { + st = 'GA'; + state = 'Georgia'; + } else if (zipcode >= 96700 && zipcode <= 96999) { + st = 'HI'; + state = 'Hawaii'; + } else if (zipcode >= 83200 && zipcode <= 83999 && zipcode != 83414) { + st = 'ID'; + state = 'Idaho'; + } else if (zipcode >= 60000 && zipcode <= 62999) { + st = 'IL'; + state = 'Illinois'; + } else if (zipcode >= 46000 && zipcode <= 47999) { + st = 'IN'; + state = 'Indiana'; + } else if (zipcode >= 50000 && zipcode <= 52999) { + st = 'IA'; + state = 'Iowa'; + } else if (zipcode >= 66000 && zipcode <= 67999) { + st = 'KS'; + state = 'Kansas'; + } else if (zipcode >= 40000 && zipcode <= 42999) { + st = 'KY'; + state = 'Kentucky'; + } else if (zipcode >= 70000 && zipcode <= 71599) { + st = 'LA'; + state = 'Louisiana'; + } else if (zipcode >= 3900 && zipcode <= 4999) { + st = 'ME'; + state = 'Maine'; + } else if (zipcode >= 20600 && zipcode <= 21999) { + st = 'MD'; + state = 'Maryland'; + } else if ((zipcode >= 1000 && zipcode <= 2799) || (zipcode == 5501) || (zipcode == 5544)) { + st = 'MA'; + state = 'Massachusetts'; + } else if (zipcode >= 48000 && zipcode <= 49999) { + st = 'MI'; + state = 'Michigan'; + } else if (zipcode >= 55000 && zipcode <= 56899) { + st = 'MN'; + state = 'Minnesota'; + } else if (zipcode >= 38600 && zipcode <= 39999) { + st = 'MS'; + state = 'Mississippi'; + } else if (zipcode >= 63000 && zipcode <= 65999) { + st = 'MO'; + state = 'Missouri'; + } else if (zipcode >= 59000 && zipcode <= 59999) { + st = 'MT'; + state = 'Montana'; + } else if (zipcode >= 27000 && zipcode <= 28999) { + st = 'NC'; + state = 'North Carolina'; + } else if (zipcode >= 58000 && zipcode <= 58999) { + st = 'ND'; + state = 'North Dakota'; + } else if (zipcode >= 68000 && zipcode <= 69999) { + st = 'NE'; + state = 'Nebraska'; + } else if (zipcode >= 88900 && zipcode <= 89999) { + st = 'NV'; + state = 'Nevada'; + } else if (zipcode >= 3000 && zipcode <= 3899) { + st = 'NH'; + state = 'New Hampshire'; + } else if (zipcode >= 7000 && zipcode <= 8999) { + st = 'NJ'; + state = 'New Jersey'; + } else if (zipcode >= 87000 && zipcode <= 88499) { + st = 'NM'; + state = 'New Mexico'; + } else if ((zipcode >= 10000 && zipcode <= 14999) || (zipcode == 6390) || (zipcode == 501) || (zipcode == 544)) { + st = 'NY'; + state = 'New York'; + } else if (zipcode >= 43000 && zipcode <= 45999) { + st = 'OH'; + state = 'Ohio'; + } else if ((zipcode >= 73000 && zipcode <= 73199) || (zipcode >= 73400 && zipcode <= 74999)) { + st = 'OK'; + state = 'Oklahoma'; + } else if (zipcode >= 97000 && zipcode <= 97999) { + st = 'OR'; + state = 'Oregon'; + } else if (zipcode >= 15000 && zipcode <= 19699) { + st = 'PA'; + state = 'Pennsylvania'; + } else if (zipcode >= 300 && zipcode <= 999) { + st = 'PR'; + state = 'Puerto Rico'; + } else if (zipcode >= 2800 && zipcode <= 2999) { + st = 'RI'; + state = 'Rhode Island'; + } else if (zipcode >= 29000 && zipcode <= 29999) { + st = 'SC'; + state = 'South Carolina'; + } else if (zipcode >= 57000 && zipcode <= 57999) { + st = 'SD'; + state = 'South Dakota'; + } else if (zipcode >= 37000 && zipcode <= 38599) { + st = 'TN'; + state = 'Tennessee'; + } else if ((zipcode >= 75000 && zipcode <= 79999) || (zipcode >= 73301 && zipcode <= 73399) || (zipcode >= 88500 && zipcode <= 88599)) { + st = 'TX'; + state = 'Texas'; + } else if (zipcode >= 84000 && zipcode <= 84999) { + st = 'UT'; + state = 'Utah'; + } else if (zipcode >= 5000 && zipcode <= 5999) { + st = 'VT'; + state = 'Vermont'; + } else if ((zipcode >= 20100 && zipcode <= 20199) || (zipcode >= 22000 && zipcode <= 24699) || (zipcode == 20598)) { + st = 'VA'; + state = 'Virginia'; + } else if ((zipcode >= 20000 && zipcode <= 20099) || (zipcode >= 20200 && zipcode <= 20599) || (zipcode >= 56900 && zipcode <= 56999)) { + st = 'DC'; + state = 'Washington DC'; + } else if (zipcode >= 98000 && zipcode <= 99499) { + st = 'WA'; + state = 'Washington'; + } else if (zipcode >= 24700 && zipcode <= 26999) { + st = 'WV'; + state = 'West Virginia'; + } else if (zipcode >= 53000 && zipcode <= 54999) { + st = 'WI'; + state = 'Wisconsin'; + } else if ((zipcode >= 82000 && zipcode <= 83199) || zipcode == 83414) { + st = 'WY'; + state = 'Wyoming'; + } else { + st = 'none'; + state = 'none'; + } + + /* Return `state` for full name or `st` for postal abbreviation */ + return st; +} diff --git a/src/lib/services/commerce/util/types.ts b/src/lib/services/commerce/util/types.ts index c6ced2d..9ed9722 100644 --- a/src/lib/services/commerce/util/types.ts +++ b/src/lib/services/commerce/util/types.ts @@ -1,3 +1,5 @@ +import type { DeepPartial } from "./misc"; + export namespace Printful { export namespace Products { export interface MetaDataSingle { @@ -84,12 +86,37 @@ export namespace Printful { } } + export namespace Orders { + export type Order = { + external_id: string, + shipping: string + recipient: { + name: string + company?: string + address1: string + address2: string + city: string + state_code: string + state_name?: string + country_code: string + country_name?: string + zip: string + phone?: string + email?: string + tax_number?: string + } + items: Array<{ external_variant_id: string, quantity: number }> + } + } + export namespace Webhook { // meta - interface MetaData { + interface MetaData { + type: E; created: number; retries: number; store: number; + data: D } export enum Event { @@ -98,32 +125,26 @@ export namespace Printful { } // product updated - export interface ProductUpdated extends MetaData { - type: Event.ProductUpdated, - data: { - sync_product: { - id: number; - external_id: string; - name: string; - variants: number; - synced: number; - thumbnail_url: string; - is_ignored: boolean; - }; - } - } + export interface ProductUpdated extends MetaData { } // product deleted - export interface ProductDeleted extends MetaData { - type: Event.ProductDeleted, - data: { - sync_product: { - id: number; - external_id: string; - name: string; - }; - } - } + export interface ProductDeleted extends MetaData { } export type EventPayload = ProductUpdated | ProductDeleted } @@ -185,4 +206,187 @@ export namespace Webflow { publishStatus?: "staging" | "live"; } } + + export namespace Orders { + export type Order = { + orderId: string + status: string + comment: string + orderComment: string + acceptedOn: string + fulfilledOn: string | null + refundedOn: string | null + disputedOn: string | null + disputeUpdatedOn: string | null + disputeLastStatus: string | null + customerPaid: { + unit: string + value: string + string: string + } + netAmount: { + unit: string + value: string + string: string + } + applicationFee: { + unit: string + value: string + string: string + } + allAddresses: Array<{ + type: string + addressee: string + line1: string + line2: string + city: string + state: string + country: string + postalCode: string + }> + shippingAddress: { + type: string + japanType: string + addressee: string + line1: string + line2: string + city: string + state: string + country: string + postalCode: string + } + billingAddress: { + type: string + addressee: string + line1: string + line2: string + city: string + state: string + country: string + postalCode: string + } + shippingProvider: string + shippingTracking: string + shippingTrackingURL: string + customerInfo: { + fullName: string + email: string + } + purchasedItems: Array<{ + count: number + rowTotal: { + unit: string + value: string + string: string + } + productId: string + productName: string + productSlug: string + variantId: string + variantName: string + variantSlug: string + variantSKU: string + variantImage: { + url: string + file: { + size: number; + originalFileName: string; + createdOn: string; + contentType: string; + width: number; + height: number; + variants: { + size: number; + url: string, + originalFileName: string; + width: number; + height: number; + }[] + } + } + variantPrice: { + unit: string + value: string + string: string + } + weight: number + width: number + height: number + length: number + }> + purchasedItemsCount: number + stripeDetails: { + subscriptionId: string | null + paymentMethod: string + paymentIntentId: string + customerId: string + chargeId: string + disputeId: string | null + refundId: string + refundReason: string + } + stripeCard: { + last4: string + brand: string + ownerName: string + expires: { + year: number + month: number + } + } + paypalDetails: Object + customData: Array + metadata: { + isBuyNow: boolean + hasDownloads: boolean + paymentProcessor: string + } + isCustomerDeleted: boolean + isShippingRequired: boolean + totals: { + subtotal: { + unit: string + value: string + string: string + } + extras: Array<{ + type: string + name: string + description: string + price: { + unit: string + value: string + string: string + } + }> + total: { + unit: string + value: string + string: string + } + } + downloadFiles: Array<{ + id: string + name: string + url: string + }> + } + } + + export namespace Webhook { + interface MetaData { + triggerType: E; + payload: D + } + + export enum Event { + OrderCreated = "ecomm_new_order", + OrderUpdated = "ecomm_order_changed" + } + + export interface OrderCreated extends MetaData { } + export interface OrderUpdated extends MetaData { } + + export type EventPayload = OrderCreated | OrderUpdated; + } } diff --git a/src/lib/services/commerce/webflow.ts b/src/lib/services/commerce/webflow.ts index c78d112..845a9bc 100644 --- a/src/lib/services/commerce/webflow.ts +++ b/src/lib/services/commerce/webflow.ts @@ -1,5 +1,6 @@ import type { Webflow } from "./util/types"; import { FetchError, type DeepPartial } from "./util/misc"; +import crypto from "node:crypto"; export default class WebflowService { static Products = class { @@ -17,7 +18,7 @@ export default class WebflowService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to create Webflow product SKU", res); + throw new FetchError("Failed to create Webflow product SKU", res); } } @@ -33,7 +34,7 @@ export default class WebflowService { }) }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to update Webflow product SKU", res); + throw new FetchError("Failed to update Webflow product SKU", res); } } } @@ -49,7 +50,7 @@ export default class WebflowService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to create Webflow product", res); + throw new FetchError("Failed to create Webflow product", res); } const createdWebflowProduct = await res.json(); @@ -65,7 +66,7 @@ export default class WebflowService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to get all Webflow products", res); + throw new FetchError("Failed to get all Webflow products", res); } const payload = await res.json(); @@ -85,7 +86,7 @@ export default class WebflowService { } if (!res.ok) { - throw await FetchError.createAndParse("Failed to get Webflow product", res); + throw new FetchError("Failed to get Webflow product", res); } const payload = await res.json(); @@ -103,7 +104,7 @@ export default class WebflowService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to update Webflow product", res); + throw new FetchError("Failed to update Webflow product", res); } } @@ -118,10 +119,56 @@ export default class WebflowService { }); if (!res.ok) { - throw await FetchError.createAndParse("Failed to remove Webflow product", res); + throw new FetchError("Failed to remove Webflow product", res); } } } + + static Util = class { + static verifyWebflowSignature(request: Request, body: unknown) { + try { + const timestamp = request.headers.get("x-webflow-timestamp"); + if (!timestamp) { + throw new Error("No timestamp provided"); + } + const providedSignature = request.headers.get("x-webflow-signature"); + if (!providedSignature) { + throw new Error("No signature provided"); + } + + const secret = env().WEBHOOK_SECRET; + if (!secret) { + throw new Error("No secret provided"); + } + + if (!body) { + throw new Error("Body is empty"); + } + + const requestTimestamp = parseInt(timestamp, 10); + const data = `${requestTimestamp}:${JSON.stringify(body)}`; + const hash = crypto.createHmac('sha256', secret) + .update(data) + .digest('hex'); + + if (!crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(providedSignature, 'hex'))) { + throw new Error('Invalid signature'); + } + + const currentTime = Date.now(); + + if (currentTime - requestTimestamp > 300000) { + throw new Error('Request is older than 5 minutes'); + } + return true; + + } catch (err) { + console.error(`Error verifying signature: ${err}`); + return false; + } + + } + } } const env = () => { @@ -133,6 +180,7 @@ const env = () => { SITE_ID: Bun.env.WEBFLOW_SITE_ID, COLLECTIONS_ID: Bun.env.WEBFLOW_COLLECTION_ID, AUTH_TOKEN: Bun.env.WEBFLOW_AUTH, + WEBHOOK_SECRET: Bun.env.WEBFLOW_WEBHOOK_SECRET }; return { diff --git a/src/routes/calculator/+page.svelte b/src/routes/calculator/+page.svelte index 54e1332..da69665 100644 --- a/src/routes/calculator/+page.svelte +++ b/src/routes/calculator/+page.svelte @@ -20,11 +20,20 @@ weight: "", }, cfg: { - enableGeneration: true, - maxLevel: "100", - weightModfier: ".1", - weightSkew: ".1", - ageModifier: ".1", + global: { + maxLevel: "100", + }, + activity: Object.fromEntries( + Object.values(Activity).map((activity) => [ + activity, + { + enableGeneration: true, + weightModfier: ".1", + weightSkew: "0", + ageModifier: ".1", + }, + ]), + ), }, }); @@ -36,11 +45,22 @@ weight: lbToKg(+input.metrics.weight), } as Metrics, cfg: { - maxLevel: +input.cfg.maxLevel, - weightModifier: +input.cfg.weightModfier, - weightSkew: +input.cfg.weightSkew, - ageModifier: +input.cfg.ageModifier, - disableGeneration: !input.cfg.enableGeneration, + global: { + maxLevel: +input.cfg.global.maxLevel, + }, + activity: Object.fromEntries( + Object.values(Activity).map((activity) => [ + activity, + { + weightModifier: + +input.cfg.activity[activity].weightModfier, + weightSkew: +input.cfg.activity[activity].weightSkew, + ageModifier: +input.cfg.activity[activity].ageModifier, + disableGeneration: + !input.cfg.activity[activity].enableGeneration, + }, + ]), + ), } as StandardsConfig, }); @@ -82,7 +102,7 @@ min="1" max="100" placeholder="5" - bind:value={input.cfg.maxLevel} + bind:value={input.cfg.global.maxLevel} /> @@ -97,7 +117,10 @@ @@ -110,8 +133,12 @@ max="1" step=".05" placeholder=".1" - bind:value={input.cfg.weightModfier} - disabled={selected.cfg.disableGeneration} + bind:value={ + input.cfg.activity[selected.activity] + .weightModfier + } + disabled={selected.cfg.activity[selected.activity] + .disableGeneration} />