-
Notifications
You must be signed in to change notification settings - Fork 5
✨ server: add runtime entrypoints for api and hooks #1227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/infra": minor | ||
| --- | ||
|
|
||
| 🧱 setup services for api and hooks |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| ✨ add runtime entrypoints for api and hooks |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,61 @@ export default define({ | |
| common: ["redis-url", "sentry-dsn"], | ||
| crema: ["redis-address", "redis-password", "redis-username"], | ||
| services: { | ||
| api: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the production or base-sepolia Cloud Run API receives browser traffic, its container has Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When enqueueing an account subscription fails during a Redis outage, Useful? React with 👍 / 👎.
Comment on lines
+4
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On the first deployment containing this AGENTS.md reference: AGENTS.md:L129-L131 Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a freshly managed production stack, Useful? React with 👍 / 👎. |
||
| secrets: [ | ||
| "auth-secret", | ||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a freshly provisioned stack, every namespaced value generated from these lists (for example, Useful? React with 👍 / 👎. |
||
| "bridge-api-key", | ||
| "intercom-identity-key", | ||
| "manteca-api-key", | ||
| "panda-api-key", | ||
| "pax-associate-id-key", | ||
| "pax-api-key", | ||
| "persona-api-key", | ||
| "postgres-url", | ||
| "sardine-api-key", | ||
| "segment-write-key", | ||
| "wallet-extension-secret", | ||
| ], | ||
| shared: [ | ||
| "bridge-api-url", | ||
| "manteca-api-url", | ||
| "panda-api-url", | ||
| "pax-api-url", | ||
| "persona-api-url", | ||
| "sardine-api-url", | ||
| ], | ||
| }, | ||
| activity: { secrets: ["alchemy-webhooks-key", "onesignal-api-key", "postgres-url"] }, | ||
| block: { secrets: ["alchemy-webhooks-key", "onesignal-api-key"], signers: ["executor"] }, | ||
|
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the GCP deployment enables these Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a proposal or withdrawal is scheduled for a future unlock, Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a freshly provisioned stack, this generated Useful? React with 👍 / 👎. |
||
| bridge: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Provisioning either the Useful? React with 👍 / 👎. |
||
| secrets: ["bridge-api-key", "onesignal-api-key", "persona-api-key", "postgres-url", "segment-write-key"], | ||
| shared: ["bridge-api-url", "persona-api-url"], | ||
| }, | ||
| chat: { | ||
| env: { WHATSAPP_PHONE_NUMBER_ID: "whatsappPhoneNumberId" }, | ||
| secrets: ["whatsapp-app-secret", "whatsapp-verify-token"], | ||
| }, | ||
| manteca: { | ||
| secrets: ["manteca-api-key", "onesignal-api-key", "postgres-url", "segment-write-key", "webhooks-key"], | ||
| shared: ["manteca-api-url"], | ||
| }, | ||
| panda: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When callbacks are directed to these standalone Cloud Run services, request-based CPU can be suspended immediately after a handler responds because the service template does not enable always-allocated CPU. This is distinct from the already-flagged block timers: the newly inspected Panda and Persona handlers acknowledge webhooks while unawaited work is still running—for example, Useful? React with 👍 / 👎. |
||
| secrets: ["onesignal-api-key", "panda-api-key", "postgres-url", "sardine-api-key", "segment-write-key"], | ||
| shared: ["panda-api-url", "sardine-api-url"], | ||
| signers: ["settler", "issuer"], | ||
| }, | ||
| persona: { | ||
| secrets: [ | ||
| "panda-api-key", | ||
| "pax-associate-id-key", | ||
| "pax-api-key", | ||
| "persona-api-key", | ||
| "persona-webhook-secret", | ||
| "postgres-url", | ||
| "sardine-api-key", | ||
| ], | ||
| shared: ["panda-api-url", "pax-api-url", "persona-api-url", "sardine-api-url"], | ||
| }, | ||
| }, | ||
| workers: { | ||
| allow: { signers: ["allower"] }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { drizzle } from "drizzle-orm/node-postgres"; | ||
| import { Redis } from "ioredis"; | ||
|
|
||
| import api from "."; | ||
| import * as schema from "../database/schema"; | ||
| import supervise, { own } from "../supervise"; | ||
| import createIntercom from "../utils/intercom"; | ||
| import createPanda from "../utils/panda"; | ||
| import createPax from "../utils/pax"; | ||
| import createPersona from "../utils/persona"; | ||
| import createBridge from "../utils/ramps/bridge"; | ||
| import createManteca from "../utils/ramps/manteca"; | ||
| import createSardine from "../utils/sardine"; | ||
| import secret from "../utils/secret"; | ||
| import createSegment from "../utils/segment"; | ||
| import createWalletExtension from "../utils/walletExtension"; | ||
| import createCredit from "../workers/credit/queue"; | ||
| import createSubscribe from "../workers/subscribe/queue"; | ||
| import { connect } from "../workers/worker"; | ||
|
|
||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "api", | ||
| Promise.all([ | ||
| secret("redis-url", secrets).then((url) => { | ||
| const bullmq = connect(url); | ||
| return [new Redis(url), bullmq, createCredit(bullmq), createSubscribe(bullmq)] as const; | ||
| }), | ||
| secret("api-auth-secret", secrets), | ||
| Promise.all([secret("api-bridge-api-key", secrets), secret("bridge-api-url", secrets)]).then(([key, url]) => | ||
| createBridge(key, url), | ||
| ), | ||
| secret("api-postgres-url", secrets).then((url) => drizzle(url, { schema })), | ||
| secret("api-intercom-identity-key", secrets).then((key) => createIntercom(key)), | ||
| Promise.all([secret("api-manteca-api-key", secrets), secret("manteca-api-url", secrets)]).then(([key, url]) => | ||
| createManteca(key, url), | ||
| ), | ||
| Promise.all([secret("api-panda-api-key", secrets), secret("panda-api-url", secrets)]).then(([key, url]) => | ||
| createPanda({ key, url }), | ||
| ), | ||
| Promise.all([ | ||
| secret("api-pax-associate-id-key", secrets), | ||
| secret("api-pax-api-key", secrets), | ||
| secret("pax-api-url", secrets), | ||
| ]).then(([associateKey, key, url]) => createPax({ associateKey, key, url })), | ||
| Promise.all([secret("api-persona-api-key", secrets), secret("persona-api-url", secrets)]).then(([key, url]) => | ||
| createPersona(key, url), | ||
| ), | ||
| Promise.all([secret("api-sardine-api-key", secrets), secret("sardine-api-url", secrets)]).then(([key, url]) => | ||
| createSardine(key, url), | ||
| ), | ||
| secret("api-segment-write-key", secrets).then((key) => createSegment(key)), | ||
| secret("api-wallet-extension-secret", secrets).then((value) => createWalletExtension(value)), | ||
| ]).then( | ||
| ([ | ||
| [redis, bullmq, credit, subscribe], | ||
| authSecret, | ||
| bridge, | ||
| database, | ||
| intercom, | ||
| manteca, | ||
| panda, | ||
| pax, | ||
| persona, | ||
| sardine, | ||
| segment, | ||
| walletExtension, | ||
| ]) => | ||
| own( | ||
| api({ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Serve the API under Useful? React with 👍 / 👎. |
||
| authSecret, | ||
| bridge, | ||
| credit, | ||
| database, | ||
| intercom, | ||
| manteca, | ||
| panda, | ||
| pax, | ||
| persona, | ||
| redis, | ||
| sardine, | ||
| segment, | ||
| subscribe, | ||
| walletExtension, | ||
| }), | ||
| () => database.$client.end(), | ||
| () => redis.quit(), | ||
| () => secrets.close(), | ||
| () => segment.close(), | ||
| () => Promise.all([credit.close(), subscribe.close()]).finally(() => bullmq.quit()), | ||
| ), | ||
| ), | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { drizzle } from "drizzle-orm/node-postgres"; | ||
| import { Redis } from "ioredis"; | ||
|
|
||
| import * as schema from "../../database/schema"; | ||
| import supervise, { own } from "../../supervise"; | ||
| import createAlchemy from "../../utils/alchemy"; | ||
| import createOnesignal from "../../utils/onesignal"; | ||
| import secret from "../../utils/secret"; | ||
| import createPoke from "../../workers/poke/queue"; | ||
| import { connect } from "../../workers/worker"; | ||
| import activity from "../activity"; | ||
|
|
||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "activity", | ||
| Promise.all([ | ||
| secret("activity-alchemy-webhooks-key", secrets).then((key) => createAlchemy(key)), | ||
| secret("activity-postgres-url", secrets).then((url) => drizzle(url, { schema })), | ||
| secret("activity-onesignal-api-key", secrets).then((key) => createOnesignal(key)), | ||
| secret("redis-url", secrets) | ||
| .then((url) => [new Redis(url), connect(url)] as const) | ||
| .then(([redis, bullmq]) => [redis, bullmq, createPoke(bullmq)] as const), | ||
| ]).then(([alchemy, database, onesignal, [redis, bullmq, poke]]) => | ||
| own( | ||
| activity({ alchemy, database, onesignal, poke, redis }), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Pass the configured activity signing key into this hook, or otherwise preserve its startup fallback. The monolith supplies Useful? React with 👍 / 👎. |
||
| () => database.$client.end(), | ||
| () => poke.close().finally(() => bullmq.quit()), | ||
| () => redis.quit(), | ||
| () => secrets.close(), | ||
| ), | ||
| ), | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { KeyManagementServiceClient } from "@google-cloud/kms"; | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { Redis } from "ioredis"; | ||
|
|
||
| import supervise, { own } from "../../supervise"; | ||
| import createAlchemy from "../../utils/alchemy"; | ||
| import createOnesignal from "../../utils/onesignal"; | ||
| import secret from "../../utils/secret"; | ||
| import { signer } from "../../utils/wallet"; | ||
| import block from "../block"; | ||
|
|
||
| const kms = new KeyManagementServiceClient(); | ||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "block", | ||
| Promise.all([ | ||
| secret("block-alchemy-webhooks-key", secrets).then((key) => createAlchemy(key)), | ||
| signer("executor", kms), | ||
| secret("block-onesignal-api-key", secrets).then((key) => createOnesignal(key)), | ||
| secret("redis-url", secrets).then((url) => new Redis(url)), | ||
| ]).then(([alchemy, executor, onesignal, redis]) => | ||
| own( | ||
| block({ alchemy, executor, onesignal, redis }), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Pass the configured block signing key into this hook, or otherwise preserve its startup fallback. The monolith supplies Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this Cloud Run instance starts after the DigitalOcean server and Useful? React with 👍 / 👎. |
||
| () => kms.close(), | ||
| () => redis.quit(), | ||
| () => secrets.close(), | ||
| ), | ||
| ), | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { drizzle } from "drizzle-orm/node-postgres"; | ||
|
|
||
| import * as schema from "../../database/schema"; | ||
| import supervise, { own } from "../../supervise"; | ||
| import createOnesignal from "../../utils/onesignal"; | ||
| import createPersona from "../../utils/persona"; | ||
| import createBridge from "../../utils/ramps/bridge"; | ||
| import secret from "../../utils/secret"; | ||
| import createSegment from "../../utils/segment"; | ||
| import bridge from "../bridge"; | ||
|
|
||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "bridge", | ||
| Promise.all([ | ||
| Promise.all([secret("bridge-bridge-api-key", secrets), secret("bridge-api-url", secrets)]).then(([key, url]) => | ||
| createBridge(key, url), | ||
| ), | ||
| secret("bridge-postgres-url", secrets).then((url) => drizzle(url, { schema })), | ||
| secret("bridge-onesignal-api-key", secrets).then((key) => createOnesignal(key)), | ||
| Promise.all([secret("bridge-persona-api-key", secrets), secret("persona-api-url", secrets)]).then(([key, url]) => | ||
| createPersona(key, url), | ||
| ), | ||
| secret("bridge-segment-write-key", secrets).then((key) => createSegment(key)), | ||
| ]).then(([provider, database, onesignal, persona, segment]) => | ||
| own( | ||
| bridge({ bridge: provider, database, onesignal, persona, segment }), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| () => database.$client.end(), | ||
| () => secrets.close(), | ||
| () => segment.close(), | ||
| ), | ||
| ), | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { drizzle } from "drizzle-orm/node-postgres"; | ||
|
|
||
| import * as schema from "../../database/schema"; | ||
| import supervise, { own } from "../../supervise"; | ||
| import createOnesignal from "../../utils/onesignal"; | ||
| import createManteca from "../../utils/ramps/manteca"; | ||
| import secret from "../../utils/secret"; | ||
| import createSegment from "../../utils/segment"; | ||
| import manteca from "../manteca"; | ||
|
|
||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "manteca", | ||
| Promise.all([ | ||
| secret("manteca-postgres-url", secrets).then((url) => drizzle(url, { schema })), | ||
| Promise.all([secret("manteca-manteca-api-key", secrets), secret("manteca-api-url", secrets)]).then(([key, url]) => | ||
| createManteca(key, url), | ||
| ), | ||
| secret("manteca-webhooks-key", secrets), | ||
| secret("manteca-onesignal-api-key", secrets).then((key) => createOnesignal(key)), | ||
| secret("manteca-segment-write-key", secrets).then((key) => createSegment(key)), | ||
| ]).then(([database, provider, mantecaWebhookKey, onesignal, segment]) => | ||
| own( | ||
| manteca({ database, manteca: provider, mantecaWebhookKey, onesignal, segment }), | ||
| () => database.$client.end(), | ||
| () => secrets.close(), | ||
| () => segment.close(), | ||
| ), | ||
| ), | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { KeyManagementServiceClient } from "@google-cloud/kms"; | ||
| import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
| import { drizzle } from "drizzle-orm/node-postgres"; | ||
|
|
||
| import * as schema from "../../database/schema"; | ||
| import supervise, { own } from "../../supervise"; | ||
| import createOnesignal from "../../utils/onesignal"; | ||
| import createPanda from "../../utils/panda"; | ||
| import createSardine from "../../utils/sardine"; | ||
| import secret from "../../utils/secret"; | ||
| import createSegment from "../../utils/segment"; | ||
| import { signer } from "../../utils/wallet"; | ||
| import createHook from "../../workers/hook/queue"; | ||
| import createRefund from "../../workers/refund/queue"; | ||
| import { connect } from "../../workers/worker"; | ||
| import panda from "../panda"; | ||
|
|
||
| const kms = new KeyManagementServiceClient(); | ||
| const secrets = new SecretManagerServiceClient(); | ||
|
|
||
| supervise( | ||
| "panda", | ||
| Promise.all([ | ||
| secret("panda-postgres-url", secrets).then((url) => drizzle(url, { schema })), | ||
| signer("issuer", kms), | ||
| secret("panda-onesignal-api-key", secrets).then((key) => createOnesignal(key)), | ||
| Promise.all([secret("panda-panda-api-key", secrets), secret("panda-api-url", secrets)]).then(([key, url]) => | ||
| createPanda({ key, url }), | ||
| ), | ||
| secret("redis-url", secrets) | ||
| .then((url) => connect(url)) | ||
| .then((bullmq) => [bullmq, createRefund(bullmq), createHook(bullmq)] as const), | ||
| Promise.all([secret("panda-sardine-api-key", secrets), secret("sardine-api-url", secrets)]).then(([key, url]) => | ||
| createSardine(key, url), | ||
| ), | ||
| secret("panda-segment-write-key", secrets).then((key) => createSegment(key)), | ||
| signer("settler", kms), | ||
| ]).then(([database, issuer, onesignal, provider, [bullmq, refund, webhook], sardine, segment, settler]) => | ||
| own( | ||
| panda({ database, issuer, onesignal, panda: provider, refund, sardine, segment, settler, webhook }), | ||
| () => database.$client.end(), | ||
| () => kms.close(), | ||
| () => secrets.close(), | ||
| () => segment.close(), | ||
| () => Promise.all([refund.close(), webhook.close()]).finally(() => bullmq.quit()), | ||
| ), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add trailing commas to compact configuration literals.
Several arrays and inline object literals omit an internal trailing comma. Add trailing commas consistently in this TypeScript file.
As per coding guidelines, use trailing commas in all structures. Based on learnings, this applies to TypeScript files.
Proposed formatting change
Also applies to: 29-30, 32-33, 36-37, 40-41, 44-46, 58-58, 62-75
Sources: Coding guidelines, Learnings