Skip to content

Comments

chore: add docker workflow#87

Merged
waltergalvao merged 6 commits intomainfrom
chore/docker-workflow
Feb 22, 2026
Merged

chore: add docker workflow#87
waltergalvao merged 6 commits intomainfrom
chore/docker-workflow

Conversation

@waltergalvao
Copy link
Contributor

@waltergalvao waltergalvao commented Feb 21, 2026

Greptile Summary

This PR adds Docker publishing workflow and improves self-hosted deployment support by making optional integrations truly optional. The changes transform environment variable validation from allowEmpty: true to default: "", allowing the API to start without Stripe, Slack, Sentry, or LogTail credentials. The workflow validates builds via health checks before publishing to Docker Hub.

Key changes:

  • New GitHub Actions workflow for building, validating, and publishing Docker images
  • Environment variables now use default: "" instead of allowEmpty: true, enabling graceful degradation
  • Stripe router conditionally loaded only in SaaS mode (when !isAppSelfHosted())
  • Health check endpoint added for container orchestration
  • Fixed critical bug in BullBoard rate limiter (was 60ms, now correctly 15 minutes)
  • Runtime guards added for Stripe and Slack client initialization to provide clear error messages
  • Sentry and LogTail now skip initialization when credentials absent instead of failing

Confidence Score: 4/5

  • Safe to merge with minor workflow configuration improvement recommended
  • The code changes are well-structured and improve self-hosted deployment experience. The critical rate limiter bug fix is important. Main concern is the workflow validation step missing APP_MODE env var (already flagged in previous comments), which could cause the container to default to an unexpected mode during testing.
  • .github/workflows/docker-publish.yml needs APP_MODE=self-hosted added to validation step

Important Files Changed

Filename Overview
.github/workflows/docker-publish.yml New workflow to build, validate, and publish Docker images; missing APP_MODE env var in validation step
apps/api/src/bull-mq/bull-board.router.ts Fixed rate limit windowMs calculation (was 60ms, now 15 minutes); added env var guards
apps/api/src/env.ts Changed env validation from allowEmpty/devDefault to default empty strings for optional integrations; fixed LOG_DRAIN description
apps/api/src/express.ts Added health router; conditionally loads stripe router only in SaaS mode; explicit CORS methods instead of wildcard

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Docker Workflow Triggered] --> B[Validate Job]
    B --> C[Build Docker Image]
    C --> D[Start Container with Test Env Vars]
    D --> E[Health Check: /health endpoint]
    E -->|Success| F[Publish Job]
    E -->|Failure| G[Show Logs & Fail]
    F --> H[Build & Push to Docker Hub]
    H --> I[Tag: version + latest]
    
    J[App Startup] --> K{APP_MODE check}
    K -->|self-hosted| L[Skip Stripe Router]
    K -->|saas| M[Load Stripe Router]
    L --> N[Load Health, GitHub, Slack, BullBoard]
    M --> N
    N --> O[Optional Services Check]
    O -->|SENTRY_DSN present| P[Init Sentry]
    O -->|LOG_DRAIN=logtail & token| Q[Init LogTail]
    O -->|BULLBOARD creds present| R[Init BullBoard]
Loading

Last reviewed commit: 1c00e5a

@dev-sweetr-dev dev-sweetr-dev bot added the medium Medium PR - Strive for smaller PRs to reduce risk and review time label Feb 21, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Walkthrough

Adds a Docker Build & Publish GitHub Actions workflow; introduces GET /health; standardizes and adds environment variable defaults; adds runtime guards for Stripe and Slack, and a Stripe client guard; gates BullBoard, LogTail, and Sentry initialization on config; updates docs for LogTail.

Changes

Cohort / File(s) Summary
CI/CD Workflow
.github/workflows/docker-publish.yml
New "Build and Publish Docker Image" workflow: buildx build, local validation container with health checks, and push to Docker Hub with version and latest tags.
API Health & Express
apps/api/src/app/health/health.router.ts, apps/api/src/express.ts
Adds GET /health returning { status: "ok" }, mounts health router, restricts CORS methods, and conditionally mounts Stripe routes only when not self-hosted.
Environment Config
apps/api/src/env.ts
Replaces many allowEmpty/devDefault declarations with explicit default: "", introduces LOGTAIL_TOKEN, adjusts LOG_DRAIN default/description, and removes the dev-default for GITHUB_WEBHOOK_SECRET.
BullBoard Gate
apps/api/src/bull-mq/bull-board.router.ts
Requires BULLBOARD_PATH plus BULLBOARD_USERNAME and BULLBOARD_PASSWORD to enable BullBoard routes and auth; fixes rate limiter window to 15 minutes.
Logging & Sentry
apps/api/src/lib/logger.ts, apps/api/src/lib/sentry.ts
Makes LogTail transport conditional on LOG_DRAIN === "logtail" and presence of LOGTAIL_TOKEN; skips Sentry init early when SENTRY_DSN is absent and simplifies enabled check to exclude only dev.
Stripe & Billing
apps/api/src/app/billing/stripe.router.ts, apps/api/src/lib/stripe.ts
Adds runtime guards: require STRIPE_API_KEY (and STRIPE_WEBHOOK_SECRET for webhook) before processing; getStripeClient throws when key is missing.
Slack Integration
apps/api/src/app/integrations/slack/services/slack-integration.service.ts
Adds guard in getInstallUrl to throw if Slack client ID is not configured.
Documentation
apps/docs/get-started/self-host.mdx
Adds LOG_DRAIN=logtail to LogTail setup instructions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: dockerize #48 — Overlaps edits to apps/api/src/env.ts and apps/api/src/lib/sentry.ts; likely related configuration and CI/CI artifacts work.

Suggested reviewers

  • sweetrdev
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'chore: add docker workflow' is only partially related to the changeset. While a Docker workflow is added, the PR also makes substantial changes to environment configuration, service initialization, and the express setup—changes that go well beyond just adding a workflow. Consider revising the title to reflect the broader scope, such as 'chore: add docker workflow and improve self-hosting support' or similar, to better represent the main changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly explains the purpose of the changes, including Docker workflow additions, environment variable handling improvements, and self-hosted deployment enhancements, all aligned with the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/docker-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/env.ts">

<violation number="1" location="apps/api/src/env.ts:38">
P1: Avoid defaulting webhook/API secrets to an empty string. This makes missing secrets silently pass validation and can run production with an empty signing key. Require these values (or limit empty defaults to dev via devDefault).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/src/bull-mq/bull-board.router.ts (1)

24-25: ⚠️ Potential issue | 🟠 Major

Pre-existing bug: windowMs: 60 is 60 milliseconds, not 15 minutes.

The comment says "15 minutes" but the value 60 is 60 ms. The rate limiter effectively allows 200 requests per 60 ms — i.e., no real throttling at all.

🐛 Proposed fix
-        windowMs: 60, // 15 minutes
+        windowMs: 15 * 60 * 1000, // 15 minutes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/bull-mq/bull-board.router.ts` around lines 24 - 25, The
rateLimiter configuration in the rateLimit(...) call incorrectly sets windowMs
to 60 (milliseconds) while the comment says 15 minutes; update windowMs in the
rateLimit call used in bull-board.router.ts (the rateLimit(...) invocation) to
15 minutes (900000 ms or use 15 * 60 * 1000) and adjust the inline comment to
match so the limiter enforces 15 minutes correctly.
apps/api/src/express.ts (1)

14-26: ⚠️ Potential issue | 🟠 Major

Removing trust proxy breaks per-client rate limiting when the container sits behind a reverse proxy.

If the app is behind a proxy/load balancer (common with Nginx, Traefik, Cloudflare, etc.), the IP address seen by Express will be the load balancer's IP, making the rate limiter effectively a global one and potentially blocking all requests once the limit is reached. Since this image is intended for self-hosted Docker deployments, users are likely to deploy it behind a reverse proxy.

Restoring the setting (or making it configurable via env var) is safer:

🛡️ Proposed fix
 export const expressApp = express();
+
+// Required when running behind a reverse proxy (Nginx, Traefik, etc.)
+expressApp.set("trust proxy", 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/express.ts` around lines 14 - 26, The app removes Express's
proxy trust which causes per-client rate limiting to see the proxy IP; update
expressApp to restore trust by calling expressApp.set('trust proxy', true) or
make it configurable via an env var (e.g., process.env.TRUST_PROXY) and apply
that value (true/false or numeric) before middlewares are mounted so rate
limiting and req.ip reflect the real client IP; locate where expressApp is
declared and configured (expressApp variable) and add the set call early in the
setup.
🧹 Nitpick comments (1)
.github/workflows/docker-publish.yml (1)

51-60: The validated image is discarded; publish rebuilds independently.

Both validate and publish build from the Dockerfile. The GHA layer cache mitigates the cost, but the published artifact is not byte-for-byte proven to be the validated one. A common alternative is to push the test image to a staging registry (or use docker buildx imagetools) so the publish job retags and pushes what was already validated, rather than rebuilding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-publish.yml around lines 51 - 60, The current
"Build image" step uses docker/build-push-action@v6 with push: false and tags
the image as ${{ env.IMAGE }}:test, so the validated image is never pushed and
the later publish job rebuilds; change the build step to push the validated
artifact to a staging registry (set push: true and tag as a stable staging tag
like :validated or :staging) or use docker buildx imagetools to create an index
and push it, then update the publish job to retag/pull that staged image and
push it to the final registry rather than rebuilding; locate the "Build image"
step, its uses entry, and the tag ${ { env.IMAGE } }:test plus the publish job
that currently rebuilds, and make the push-and-retag flow so publish operates on
the exact validated image.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/env.ts`:
- Around line 71-75: The LOG_DRAIN env var's description is incorrect (it
currently repeats the GitHub webhook secret text); update the desc for the
LOG_DRAIN str() entry to a correct, concise description such as "Where to send
logs (e.g., 'logtail' or 'console')" so it matches the choices and default in
the LOG_DRAIN definition and ensure the change is made in the LOG_DRAIN constant
in env.ts.

In `@apps/api/src/express.ts`:
- Line 26: The CORS config using use(cors({ origin: env.FRONTEND_URL,
credentials: true, methods: ["*"] })) must be changed because the cors package
and credentialed requests do not accept "*" for methods; replace the methods
array with an explicit list: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE",
"OPTIONS"] so preflight checks succeed for credentialed requests while keeping
origin: env.FRONTEND_URL and credentials: true unchanged.

---

Outside diff comments:
In `@apps/api/src/bull-mq/bull-board.router.ts`:
- Around line 24-25: The rateLimiter configuration in the rateLimit(...) call
incorrectly sets windowMs to 60 (milliseconds) while the comment says 15
minutes; update windowMs in the rateLimit call used in bull-board.router.ts (the
rateLimit(...) invocation) to 15 minutes (900000 ms or use 15 * 60 * 1000) and
adjust the inline comment to match so the limiter enforces 15 minutes correctly.

In `@apps/api/src/express.ts`:
- Around line 14-26: The app removes Express's proxy trust which causes
per-client rate limiting to see the proxy IP; update expressApp to restore trust
by calling expressApp.set('trust proxy', true) or make it configurable via an
env var (e.g., process.env.TRUST_PROXY) and apply that value (true/false or
numeric) before middlewares are mounted so rate limiting and req.ip reflect the
real client IP; locate where expressApp is declared and configured (expressApp
variable) and add the set call early in the setup.

---

Nitpick comments:
In @.github/workflows/docker-publish.yml:
- Around line 51-60: The current "Build image" step uses
docker/build-push-action@v6 with push: false and tags the image as ${{ env.IMAGE
}}:test, so the validated image is never pushed and the later publish job
rebuilds; change the build step to push the validated artifact to a staging
registry (set push: true and tag as a stable staging tag like :validated or
:staging) or use docker buildx imagetools to create an index and push it, then
update the publish job to retag/pull that staged image and push it to the final
registry rather than rebuilding; locate the "Build image" step, its uses entry,
and the tag ${ { env.IMAGE } }:test plus the publish job that currently
rebuilds, and make the push-and-retag flow so publish operates on the exact
validated image.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 21, 2026
coderabbitai[bot]
coderabbitai bot previously requested changes Feb 21, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/env.ts`:
- Around line 41-64: Add presence checks for credentials before creating SDK
instances: in getStripeClient() verify env.STRIPE_API_KEY is non-empty and
return null/undefined (or throw a clear, documented error) instead of
instantiating Stripe with an empty key, and update callers to guard against a
null client; in getInstallUrl() verify env.SLACK_CLIENT_ID (the clientId used by
getInstallUrl()/getIntegration()) is non-empty before building the OAuth URL and
return a disabled/no-op response when missing. Follow the same guard pattern
used in sentry.ts, logger.ts, and bull-board.router.ts so integrations are
gracefully disabled when STRIPE_API_KEY or SLACK_CLIENT_ID are empty.

Comment on lines 41 to 64
STRIPE_API_KEY: str({
desc: "The secret API Key for Stripe access",
allowEmpty: true,
devDefault: "",
default: "",
}),
STRIPE_WEBHOOK_SECRET: str({
desc: "The secret string used to sign Stripe webhooks",
allowEmpty: true,
devDefault: "",
default: "",
}),
SLACK_CLIENT_ID: str({
desc: "The public Slack Client ID",
allowEmpty: true,
devDefault: "",
default: "",
}),
SLACK_CLIENT_SECRET: str({
desc: "The secret API Key for Slack access",
allowEmpty: true,
devDefault: "",
default: "",
}),
SLACK_WEBHOOK_SECRET: str({
desc: "The secret string used to sign Slack webhooks",
allowEmpty: true,
devDefault: "",
default: "",
}),
SLACK_INSTALL_NOTIFICATION_WEBHOOK_URL: str({
desc: "The URL to the Slack install notification webhook",
allowEmpty: true,
devDefault: "",
default: "",
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for Stripe client/SDK initialization
rg -nP --type=ts "stripe|STRIPE" --max-count=200 | head -100

Repository: sweetr-dev/sweetr.dev

Length of output: 3276


🏁 Script executed:

# Search for Slack client/SDK initialization  
rg -nP --type=ts "slack|SLACK" --max-count=200 | head -100

Repository: sweetr-dev/sweetr.dev

Length of output: 11198


🏁 Script executed:

cat -n apps/api/src/lib/stripe.ts

Repository: sweetr-dev/sweetr.dev

Length of output: 407


🏁 Script executed:

cat -n apps/api/src/app/integrations/slack/services/slack-integration.service.ts | head -120

Repository: sweetr-dev/sweetr.dev

Length of output: 4003


🏁 Script executed:

cat -n apps/api/src/config/slack.config.ts

Repository: sweetr-dev/sweetr.dev

Length of output: 542


🏁 Script executed:

cat -n apps/api/src/app/billing/stripe.router.ts

Repository: sweetr-dev/sweetr.dev

Length of output: 2179


🏁 Script executed:

# Check if stripe client is instantiated conditionally anywhere
rg -n "getStripeClient" --type=ts -B 3 -A 3

Repository: sweetr-dev/sweetr.dev

Length of output: 6037


🏁 Script executed:

# Find getSlackClient definition
rg -n "export.*getSlackClient" --type=ts -A 10 apps/api/src/app/integrations/slack/services/slack-client.service.ts

Repository: sweetr-dev/sweetr.dev

Length of output: 459


🏁 Script executed:

# Check where getInstallUrl is called and if it has guards
rg -n "getInstallUrl" --type=ts -B 5 -A 5

Repository: sweetr-dev/sweetr.dev

Length of output: 3428


🏁 Script executed:

# Check if Stripe is gated anywhere or if billing is feature-flagged
rg -n "STRIPE_API_KEY|getStripeClient" --type=ts -B 5 -A 5 | grep -E "(if|guard|feature|enabled|check)" -B 3 -A 3

Repository: sweetr-dev/sweetr.dev

Length of output: 1896


Add guards to Stripe and Slack initialization code before instantiating SDKs with potentially empty credentials.

Stripe's getStripeClient() currently instantiates the SDK unconditionally (apps/api/src/lib/stripe.ts:9), and all routes call it without checking if STRIPE_API_KEY is configured. Similarly, Slack's getInstallUrl() (slack-integration.service.ts:118) builds an OAuth URL without verifying clientId is non-empty, even though getIntegration() has such a guard. With both integrations now defaulting to empty strings in env.ts, these SDKs will fail at runtime with cryptic errors instead of gracefully disabling. Add checks like the ones in sentry.ts, logger.ts, and bull-board.router.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/env.ts` around lines 41 - 64, Add presence checks for
credentials before creating SDK instances: in getStripeClient() verify
env.STRIPE_API_KEY is non-empty and return null/undefined (or throw a clear,
documented error) instead of instantiating Stripe with an empty key, and update
callers to guard against a null client; in getInstallUrl() verify
env.SLACK_CLIENT_ID (the clientId used by getInstallUrl()/getIntegration()) is
non-empty before building the OAuth URL and return a disabled/no-op response
when missing. Follow the same guard pattern used in sentry.ts, logger.ts, and
bull-board.router.ts so integrations are gracefully disabled when STRIPE_API_KEY
or SLACK_CLIENT_ID are empty.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +93 to +122
publish:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./apps/api/Dockerfile
push: true
tags: |
${{ env.IMAGE }}:${{ inputs.version }}
${{ env.IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publish job rebuilds instead of reusing validated image. The validate job builds and tests the image, but the publish job builds a completely new image from source. Even with GHA cache, a cache miss or non-deterministic build step could mean the published image differs from the one validated. Consider saving the validated image as an artifact (e.g., via docker save / docker load or a shared registry tag) and pushing that instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
apps/api/src/app/integrations/slack/services/slack-integration.service.ts (1)

119-121: Prefer IntegrationException over bare Error for consistency.

Every other thrown error in this file uses IntegrationException (lines 26 and 74). Throwing a raw Error here means any middleware or GraphQL error handler that specifically catches/formats IntegrationException instances will treat this differently — potentially leaking a raw stack trace or bypassing structured error formatting.

♻️ Proposed fix
-  if (!config.slack.clientId) {
-    throw new Error("SLACK_CLIENT_ID is not set");
-  }
+  if (!config.slack.clientId) {
+    throw new IntegrationException("Slack is not configured for this instance.", {
+      extra: { reason: "SLACK_CLIENT_ID is not set" },
+    });
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/integrations/slack/services/slack-integration.service.ts`
around lines 119 - 121, Replace the raw Error thrown when config.slack.clientId
is missing with the project's IntegrationException so error handling stays
consistent; locate the check that references config.slack.clientId in
slack-integration.service.ts and throw new IntegrationException with a clear
message (matching other uses at lines where IntegrationException is used)
instead of new Error, ensuring imports include IntegrationException if not
already present.
apps/api/src/app/billing/stripe.router.ts (1)

51-53: Redundant guard and duplicated error message.

Two minor issues:

  1. The STRIPE_API_KEY check here is redundant. Unlike the webhook handler, the checkout handler has no inner try/catch, so getStripeClient() (called transitively via createStripeCheckoutSession on line 69) would already throw and propagate cleanly through catchErrors without being swallowed.

  2. The error message "STRIPE_API_KEY is not set" now lives in three places: stripe.ts line 8, stripe.router.ts line 20, and here on line 52. If the message or logic changes, all three must stay in sync.

Consider removing lines 51–53 and relying on getStripeClient()'s own guard, which is sufficient for routes without an inner try/catch.

♻️ Suggested cleanup
   catchErrors(async (req, res) => {
-    if (!env.STRIPE_API_KEY) {
-      throw new Error("STRIPE_API_KEY is not set");
-    }
-
     const schema = z.object({
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/billing/stripe.router.ts` around lines 51 - 53, Remove the
redundant STRIPE_API_KEY guard in the checkout route: delete the if-check that
throws "STRIPE_API_KEY is not set" and rely on getStripeClient() (used
transitively by createStripeCheckoutSession) to perform the environment check
and throw; this avoids duplicating the error message across stripe.ts,
stripe.router.ts, and the checkout handler and keeps error handling centralized
so catchErrors will propagate the original error from getStripeClient().
apps/api/src/lib/stripe.ts (1)

7-11: Move the cache-hit fast path before the guard.

The STRIPE_API_KEY guard (lines 7–9) fires on every call, including all the cache-hit returns on line 11. Since env vars are stable for the process lifetime, the check is redundant once the client has already been constructed. Reordering puts the cheapest exit first:

♻️ Suggested reorder
 export const getStripeClient = (): Stripe => {
+  if (stripeClient) return stripeClient;
+
   if (!env.STRIPE_API_KEY) {
     throw new Error("STRIPE_API_KEY is not set");
   }
 
-  if (stripeClient) return stripeClient;
-
   stripeClient = new Stripe(env.STRIPE_API_KEY);
 
   return stripeClient;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/lib/stripe.ts` around lines 7 - 11, The current guard throws if
env.STRIPE_API_KEY is missing before checking the cached stripeClient, which
forces the env check on every call; move the cache-hit fast path so the `if
(stripeClient) return stripeClient;` executes before inspecting
`env.STRIPE_API_KEY`. Update the initialization flow in the factory/initializer
function that references `stripeClient` and `env.STRIPE_API_KEY` so you only
validate the env var when you actually need to create a new client.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/src/app/billing/stripe.router.ts`:
- Around line 51-53: Remove the redundant STRIPE_API_KEY guard in the checkout
route: delete the if-check that throws "STRIPE_API_KEY is not set" and rely on
getStripeClient() (used transitively by createStripeCheckoutSession) to perform
the environment check and throw; this avoids duplicating the error message
across stripe.ts, stripe.router.ts, and the checkout handler and keeps error
handling centralized so catchErrors will propagate the original error from
getStripeClient().

In `@apps/api/src/app/integrations/slack/services/slack-integration.service.ts`:
- Around line 119-121: Replace the raw Error thrown when config.slack.clientId
is missing with the project's IntegrationException so error handling stays
consistent; locate the check that references config.slack.clientId in
slack-integration.service.ts and throw new IntegrationException with a clear
message (matching other uses at lines where IntegrationException is used)
instead of new Error, ensuring imports include IntegrationException if not
already present.

In `@apps/api/src/lib/stripe.ts`:
- Around line 7-11: The current guard throws if env.STRIPE_API_KEY is missing
before checking the cached stripeClient, which forces the env check on every
call; move the cache-hit fast path so the `if (stripeClient) return
stripeClient;` executes before inspecting `env.STRIPE_API_KEY`. Update the
initialization flow in the factory/initializer function that references
`stripeClient` and `env.STRIPE_API_KEY` so you only validate the env var when
you actually need to create a new client.

@waltergalvao waltergalvao merged commit 68cf544 into main Feb 22, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

medium Medium PR - Strive for smaller PRs to reduce risk and review time

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants