Skip to content

Feat fireflies wizard#64

Merged
positonic merged 4 commits intomainfrom
feat-fireflies-wizard
Jan 14, 2026
Merged

Feat fireflies wizard#64
positonic merged 4 commits intomainfrom
feat-fireflies-wizard

Conversation

@positonic
Copy link
Copy Markdown
Owner

@positonic positonic commented Jan 14, 2026

Description

Brief description of what this PR does.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring

Database Changes

  • This PR includes database schema changes (migrations)
  • NO database changes in this PR

If migrations are included:

  • Migration name(s):
  • Schema changes:
  • Coordinated with team: Yes/No

Testing

  • Tested locally
  • Tested in preview deployment
  • Added/updated tests
  • All tests passing

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • My changes generate no new warnings
  • Database migrations tested on test database
  • Coordinated with team on any migration conflicts

Deployment Notes

Any special deployment considerations?

Related Issues

Closes #

Summary by CodeRabbit

  • Documentation

    • Updated WhatsApp gateway instructions with a new agent chat page link.
  • New Features

    • Added a Fireflies integration setup wizard and related API endpoints.
    • Added a quick action creation endpoint for simplified task creation.
  • Privacy & Policy

    • Expanded privacy policy with detailed Google data access, usage, sharing, retention, and user rights.
  • Security

    • Introduced credential encryption/decryption utilities and moved sensitive credential storage to encrypted form.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
exponential Ready Ready Preview, Comment Jan 14, 2026 4:46pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Adds a Fireflies integration feature (frontend wizard + five backend endpoints), encrypts and decrypts stored integration credentials across several services, expands privacy policy content for Google data, and updates a WhatsApp docs link.

Changes

Cohort / File(s) Summary
Fireflies Integration Backend
src/server/api/routers/mastra.ts
Adds protected endpoints: firefliesCheckExisting, firefliesTestApiKey, firefliesCreateIntegration, firefliesGenerateWebhookToken, firefliesGetWebhookUrl, plus quickCreateAction. Includes validation, ownership/team checks, token generation, and parse helpers.
Fireflies Integration Frontend
src/app/_components/ManyChat.tsx
Adds Fireflies tooling to assistant capabilities and inserts a multi-step FIREFLIES INTEGRATION WIZARD narrative and tool list (firefliesCheckExisting, firefliesTestApiKey, firefliesCreateIntegration, firefliesGenerateWebhookToken, firefliesGetWebhookUrl).
Credential Encryption Utilities
src/server/utils/credentialHelper.ts, src/server/utils/encryption.ts
New helpers: encryptCredential, decryptCredential, getDecryptedKey, plus encryptToBase64, decryptFromBase64, isEncryptionAvailable. Provide base64-wrapped encryption/decryption with graceful fallback and availability checks.
Encrypted Credential Persistence
src/server/api/routers/integration.ts, src/server/api/routers/projectWorkflow.ts
Replace plaintext credential storage with encryptedKey usage (encrypted values + isEncrypted) across Notion, Slack, WhatsApp, Monday, Fireflies, etc. Adds calls to encryptCredential before persisting credentials.
Service Decryption & Access Scope
src/server/services/FirefliesSyncService.ts, src/server/services/notion-integration.ts, src/server/services/github-integration.ts
Fireflies: decrypts API key via getDecryptedKey, expands query scope to include team membership, handles decryption failures. Notion & GitHub: encrypt tokens before storing.
Privacy Policy / UI
src/app/(sidemenu)/privacy/page.tsx
Substantially restructures and expands Google data handling sections, user rights list, styling and links, and analytics & data-sharing language.
Docs / Small Content Change
content/docs/features/whatsapp-gateway.md
Text-only update: Step 1 wording changed to point users to the agent chat page (https://www.exponential.im/agent).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Frontend (ManyChat UI)
    participant Server as API Server (mastra)
    participant DB as Database
    participant Fireflies as Fireflies API

    User->>Client: Start Fireflies setup
    Client->>Server: firefliesCheckExisting(user/team)
    Server->>DB: Query integrations for user or team
    DB-->>Server: Return existing integration info
    Server-->>Client: Show status

    Client->>Server: firefliesTestApiKey(apiKey)
    Server->>Fireflies: Validate API key
    Fireflies-->>Server: Validation result
    Server-->>Client: Show validation

    Client->>Server: firefliesCreateIntegration(apiKey, scope)
    Server->>Fireflies: Test & register integration
    Fireflies-->>Server: Confirm
    Server->>DB: Encrypt credentials & persist integration/credential records
    DB-->>Server: Confirm storage
    Server-->>Client: Return integration details

    Client->>Server: firefliesGenerateWebhookToken()
    Server->>DB: Store generated webhook token (encrypted/expiring)
    DB-->>Server: Confirm
    Server-->>Client: Return token & webhook info

    Client->>Server: firefliesGetWebhookUrl()
    Server-->>Client: Return webhook URL and environment config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through code with eager paws,
Encrypted keys and Fireflies' laws,
Five new routes and wizarded light,
Privacy polished, tokens tight,
The rabbit cheers — integrations delight!

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely a blank template with no details filled in—no actual description, type of change selected, testing info, or related issues provided. Complete the PR description template with specific details: describe the credential encryption implementation, select the 'New feature' type, confirm no database changes, document testing performed, and reference any related issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat fireflies wizard' is vague and uses non-descriptive shorthand that doesn't clearly convey the full scope of changes. Expand the title to be more specific and descriptive, such as 'Add Fireflies integration wizard with credential encryption' to better reflect the actual implementation.

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

✨ Finishing touches
  • 📝 Generate docstrings

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
Copy Markdown

@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: 4

🤖 Fix all issues with AI agents
In `@src/server/api/routers/mastra.ts`:
- Around line 776-827: quickCreateAction currently hardcodes source: "whatsapp",
which misattributes actions created from other UIs; update quickCreateAction to
accept an optional source in its input (e.g., add source: z.string().optional()
or a z.enum of allowed sources) and use that value when creating the action
(falling back to a sensible default like null or "unknown" if not provided).
Modify the input schema for quickCreateAction, propagate input.source into the
ctx.db.action.create data.source field instead of the hardcoded "whatsapp", and
ensure any returned payload types/serializers account for the optional source
value.
- Around line 1446-1454: The Fireflies integration is saving the API key as
plaintext by passing isEncrypted: false to ctx.db.integrationCredential.create;
replace this with the existing encryption flow: call encryptString(...) from
src/server/utils/encryption.ts on input.apiKey, store the encrypted value as the
key, and set isEncrypted: true (or simply remove the explicit isEncrypted flag
to rely on the DB default), updating the create call in mastra.ts
(ctx.db.integrationCredential.create) accordingly; apply the same fix to other
endpoints that set isEncrypted: false (e.g., integration.ts,
projectWorkflow.ts).
- Around line 1433-1444: getFirefliesIntegration and
getUserFirefliesIntegrations only filter by userId so they miss team-scoped
integrations; update both methods to mirror the OR logic used in
firefliesCheckExisting and firefliesGenerateWebhookToken so queries return
integrations where userId = current user OR teamId is in teams the user belongs
to. Specifically, change the Prisma/DB filters in getFirefliesIntegration and
getUserFirefliesIntegrations to include an OR clause that checks for { userId:
userId } OR { team: { members: { some: { userId } } } } (or teamId equals
provided teamId where appropriate), ensuring team-scoped integrations are
returned for users who are team members.
🧹 Nitpick comments (1)
src/server/api/routers/mastra.ts (1)

1521-1533: Consider expanding production detection heuristic.

Line 1531's production detection (baseUrl.includes('vercel.app') || !baseUrl.includes('localhost')) may miss custom production domains. However, this is informational only and doesn't affect functionality.

More robust production check
-        isProduction: baseUrl.includes('vercel.app') || !baseUrl.includes('localhost'),
+        isProduction: process.env.NODE_ENV === 'production' || 
+                      (!baseUrl.includes('localhost') && !baseUrl.includes('127.0.0.1')),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b43d3bc and 77753b6.

📒 Files selected for processing (4)
  • content/docs/features/whatsapp-gateway.md
  • src/app/(sidemenu)/privacy/page.tsx
  • src/app/_components/ManyChat.tsx
  • src/server/api/routers/mastra.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}: NEVER use hardcoded hex colors (#123456, #fff, etc.)
NEVER use RGB/RGBA colors (rgb(), rgba())
ALWAYS use Tailwind classes or CSS variables for colors

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
  • src/server/api/routers/mastra.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Add JSDoc/documentation comments for public functions
Include example usage in documentation/comments where appropriate
Explain non-obvious implementation details in comments
Use the function keyword for pure functions
Avoid unnecessary curly braces in simple conditionals; prefer concise syntax

**/*.{ts,tsx,js,jsx}: Use nullish coalescing operator (??) instead of logical OR (||) operator
All promises must be awaited or explicitly handled with .catch() or .then()
Only await actual promises, not non-promise values
Do not have unused variables or imports
Use descriptive variable names with auxiliary verbs like isLoading, hasError, canSubmit

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
  • src/server/api/routers/mastra.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Verify import/export syntax compatibility (ESM)
Check for proper TypeScript types throughout the codebase
Implement proper type definitions for variables, functions, and return types
Avoid type assertions unless necessary
Use proper generic constraints on TypeScript generics
Use proper TypeScript types for all variables, functions, and return types
Prefer interfaces over type aliases for object shapes
Avoid enums; use maps/objects instead
Use 'nuqs' for URL search parameter state management

**/*.{ts,tsx}: Use Tailwind CSS classes and CSS variables for styling instead of inline styles with hardcoded colors
Do not use any types - use proper TypeScript types instead
Use proper type imports with import type {} syntax for type-only imports
Prefer interfaces over types for defining object shapes in TypeScript
Use Mantine v7 components with Tailwind CSS for styling
Prefer Next.js Link over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation instead of separate Link wrappers
Mantine components should use theme configuration from /src/styles/mantineTheme.ts

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
  • src/server/api/routers/mastra.ts
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

src/app/**/*.{ts,tsx}: Use App Router patterns correctly in Next.js
Follow Server Component best practices
Handle client/server code separation properly (use client only when needed)
Minimize use of 'use client', useEffect, and setState; favor React Server Components

Follow Next.js App Router patterns with Server Components preferred over Client Components

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Prefer Next.js over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation
Use declarative JSX

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

Use functional React components with TypeScript interfaces for props

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
**/*.{tsx,jsx,css,scss,sass}

📄 CodeRabbit inference engine (.cursorrules)

Use Tailwind for components and styling; implement responsive, mobile-first design

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
**/*.{ts,tsx,css,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,css,js,jsx}: NEVER USE HARDCODED HEX COLORS like #262626, #C1C2C5, #373A40, #1a1b1e. Use Tailwind classes (e.g., bg-background-primary) or CSS variables (var(--background-primary)) instead
NEVER USE RGB/RGBA COLORS like rgb(26, 27, 30) or rgba(0, 0, 0, 0.5). Use Tailwind classes or CSS variables instead
Use semantic color names for different UI elements (e.g., bg-background-primary, text-text-primary, border-border-primary)
Use CSS variables from /src/styles/globals.css configured for theme switching

Files:

  • src/app/_components/ManyChat.tsx
  • src/app/(sidemenu)/privacy/page.tsx
  • src/server/api/routers/mastra.ts
src/server/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/server/api/**/*.{ts,tsx}: All API logic must use tRPC for end-to-end type safety instead of other API patterns
Use authentication middleware to protect authenticated endpoints in tRPC routers

Files:

  • src/server/api/routers/mastra.ts
src/server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prisma with proper error handling for database operations

Files:

  • src/server/api/routers/mastra.ts
src/server/api/routers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Accept optional workspaceId in tRPC router inputs and filter database queries accordingly

Files:

  • src/server/api/routers/mastra.ts
🧠 Learnings (1)
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/routers/**/*.{ts,tsx} : Accept optional workspaceId in tRPC router inputs and filter database queries accordingly

Applied to files:

  • src/server/api/routers/mastra.ts
🧬 Code graph analysis (1)
src/server/api/routers/mastra.ts (2)
src/server/api/trpc.ts (1)
  • protectedProcedure (208-220)
src/server/api/routers/integration.ts (1)
  • testFirefliesConnection (12-59)
🔇 Additional comments (7)
src/app/_components/ManyChat.tsx (1)

116-157: Well-structured Fireflies integration wizard flow.

The wizard instructions provide a clear, secure multi-step flow for the AI agent. The security note on line 133 ("NEVER echo the API key back to the user") is an important safeguard.

A few observations:

  1. The tool names (firefliesCheckExisting, firefliesTestApiKey, etc.) align with the backend endpoints in mastra.ts.
  2. The webhook URL in step 5 (line 146) is hardcoded to https://app.fireflies.ai/integrations/custom - this is correct for Fireflies' external dashboard.
src/app/(sidemenu)/privacy/page.tsx (2)

39-199: Comprehensive Google User Data section for API compliance.

This section appropriately addresses Google API Services User Data Policy requirements with clear subsections for data access, usage, sharing, storage, retention, and revocation. The structure follows Google's Limited Use requirements well.

Minor note: Line 99 mentions specific service providers (Vercel, Railway). Ensure this list stays current if infrastructure changes.


221-238: Structured rights list improves clarity.

The refactored "Your Rights" section with explicit Access, Correction, Deletion, Data Portability, and Withdraw Consent items provides clearer guidance than the previous generic text.

src/server/api/routers/mastra.ts (3)

1334-1377: LGTM - firefliesCheckExisting endpoint.

The endpoint correctly checks for existing ACTIVE Fireflies integrations for either the user or their team. The OR clause properly handles both scopes.


1379-1391: LGTM - Secure API key testing.

The endpoint correctly validates the API key without persisting or echoing it back. The security comment on line 1389 aligns with the wizard instructions in ManyChat.tsx.


1464-1519: LGTM - Secure webhook token generation.

The endpoint properly verifies integration ownership (including team membership check) before generating tokens. The 32-character hex token using crypto.randomBytes(16) provides sufficient entropy for webhook authentication.

content/docs/features/whatsapp-gateway.md (1)

20-20: LGTM - Documentation link update.

The link correctly points to the agent chat page at https://www.exponential.im/agent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +776 to +827
// Natural language action creation - parses dates and matches project names
quickCreateAction: protectedProcedure
.input(z.object({
text: z.string().min(1),
}))
.mutation(async ({ ctx, input }) => {
const userId = ctx.session.user.id;

// Use the same parsing logic as action.quickCreate
const { parseActionInput } = await import("~/server/services/parsing/parseActionInput");
const parsed = await parseActionInput(input.text, userId, ctx.db);

// Get kanban order if project specified
let kanbanOrder: number | null = null;
if (parsed.projectId) {
const highestOrder = await ctx.db.action.findFirst({
where: { projectId: parsed.projectId, kanbanOrder: { not: null } },
orderBy: { kanbanOrder: 'desc' },
select: { kanbanOrder: true },
});
kanbanOrder = (highestOrder?.kanbanOrder ?? 0) + 1;
}

const action = await ctx.db.action.create({
data: {
name: parsed.name,
projectId: parsed.projectId,
priority: "Quick",
status: "ACTIVE",
createdById: userId,
dueDate: parsed.dueDate,
source: "whatsapp",
kanbanStatus: parsed.projectId ? "TODO" : null,
kanbanOrder,
},
include: {
project: { select: { id: true, name: true } },
},
});

return {
success: true,
action: {
id: action.id,
name: action.name,
priority: action.priority,
dueDate: action.dueDate?.toISOString(),
project: action.project,
},
parsing: parsed.parsingMetadata,
};
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Source field hardcoded to "whatsapp" may be incorrect.

The quickCreateAction mutation hardcodes source: "whatsapp" on line 807, but this endpoint appears to be a general-purpose natural language action creator. If this endpoint is called from contexts other than WhatsApp (e.g., the chat interface), the source attribution will be misleading.

Suggested fix

Consider accepting source as an optional input parameter:

  quickCreateAction: protectedProcedure
    .input(z.object({
      text: z.string().min(1),
+     source: z.string().optional().default("chat"),
    }))
    .mutation(async ({ ctx, input }) => {
      // ...
      const action = await ctx.db.action.create({
        data: {
          // ...
-         source: "whatsapp",
+         source: input.source,
          // ...
        },
      });
🤖 Prompt for AI Agents
In `@src/server/api/routers/mastra.ts` around lines 776 - 827, quickCreateAction
currently hardcodes source: "whatsapp", which misattributes actions created from
other UIs; update quickCreateAction to accept an optional source in its input
(e.g., add source: z.string().optional() or a z.enum of allowed sources) and use
that value when creating the action (falling back to a sensible default like
null or "unknown" if not provided). Modify the input schema for
quickCreateAction, propagate input.source into the ctx.db.action.create
data.source field instead of the hardcoded "whatsapp", and ensure any returned
payload types/serializers account for the optional source value.

Comment on lines +1327 to +1331
// ============================================
// FIREFLIES INTEGRATION WIZARD ENDPOINTS
// These endpoints are designed for the AI agent to guide users through
// Fireflies configuration via conversational chat
// ============================================
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, examine the Fireflies endpoints in mastra.ts
echo "=== Examining mastra.ts lines 1320-1350 ==="
sed -n '1320,1350p' src/server/api/routers/mastra.ts

echo ""
echo "=== Checking Integration model in Prisma schema ==="
# Find Prisma schema file
find . -name "*.prisma" -type f | head -5

Repository: positonic/exponential

Length of output: 1209


🏁 Script executed:

#!/bin/bash
# Get the Integration model definition from Prisma schema
cat src/prisma/schema.prisma | grep -A 20 "^model Integration"

Repository: positonic/exponential

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Check for workspaceId usage pattern in other endpoints of mastra.ts
echo "=== Searching for workspaceId in mastra.ts ==="
rg -n "workspaceId" src/server/api/routers/mastra.ts | head -20

echo ""
echo "=== Searching for teamId in mastra.ts ==="
rg -n "teamId" src/server/api/routers/mastra.ts | head -20

Repository: positonic/exponential

Length of output: 614


🏁 Script executed:

#!/bin/bash
# Find Prisma schema file
echo "=== Finding Prisma schema ==="
find . -name "schema.prisma" -o -name "*.prisma" | head -10

Repository: positonic/exponential

Length of output: 117


🏁 Script executed:

#!/bin/bash
# Check the directory structure and find where prisma schema is
ls -la | grep -i prisma
find . -path "./node_modules" -prune -o -type d -name "prisma" -print 2>/dev/null

Repository: positonic/exponential

Length of output: 132


🏁 Script executed:

#!/bin/bash
# Search for Integration model definition across the codebase
rg "model Integration" --type-add 'prisma:*.prisma' -A 15

Repository: positonic/exponential

Length of output: 3778


🏁 Script executed:

#!/bin/bash
# Check if other routers use workspaceId pattern
echo "=== Checking workspaceId usage in other router files ==="
find src/server/api/routers -name "*.ts" -type f | head -10
rg "workspaceId" src/server/api/routers/ -l

Repository: positonic/exponential

Length of output: 906


🏁 Script executed:

#!/bin/bash
# Sample a few router files to understand the workspaceId pattern
echo "=== Checking workspace.ts for workspaceId usage ==="
rg "workspaceId" src/server/api/routers/workspace.ts | head -10

echo ""
echo "=== Checking workflow.ts for workspaceId/teamId usage ==="
rg "workspaceId|teamId" src/server/api/routers/workflow.ts -A 2 | head -20

echo ""
echo "=== Checking project.ts ==="
rg "workspaceId|teamId" src/server/api/routers/project.ts -A 2 | head -20

Repository: positonic/exponential

Length of output: 1509


🏁 Script executed:

#!/bin/bash
# Look for Team/Workspace model relationship
echo "=== Checking Team model definition ==="
rg "^model (Team|Workspace)" prisma/schema.prisma -A 10

Repository: positonic/exponential

Length of output: 2819


🏁 Script executed:

#!/bin/bash
# Check if there's a Workspace model at all
rg "model Workspace" prisma/schema.prisma -A 15

Repository: positonic/exponential

Length of output: 1789


Add workspaceId support to Fireflies integration endpoints.

The Fireflies endpoints use teamId for scoping, but per the workspace scoping pattern used throughout the codebase (workflow.ts, project.ts, etc.), these endpoints should also accept optional workspaceId in their input and filter queries accordingly. Currently, the Integration model only supports teamId, not workspaceId, which limits workspace-level access patterns. Update the endpoints to align with the architectural pattern where workspaceId is the primary scope for multi-tenant filtering.

Comment thread src/server/api/routers/mastra.ts
Comment on lines +1446 to +1454
// Create the credential
await ctx.db.integrationCredential.create({
data: {
key: input.apiKey,
keyType: 'API_KEY',
isEncrypted: false, // TODO: implement encryption
integrationId: integration.id,
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the specific file and lines mentioned in the review
head -1454 src/server/api/routers/mastra.ts | tail -20

Repository: positonic/exponential

Length of output: 712


🏁 Script executed:

# Check file size first to understand scope
wc -l src/server/api/routers/mastra.ts

Repository: positonic/exponential

Length of output: 102


🏁 Script executed:

# Search for encryption-related functions or utilities in the codebase
rg -i "encrypt|cipher|aes" --type ts --type tsx -A 3

Repository: positonic/exponential

Length of output: 92


🏁 Script executed:

# Search for other integrationCredential.create calls to see if pattern is consistent
rg "integrationCredential\.create" --type ts --type tsx -B 3 -A 5

Repository: positonic/exponential

Length of output: 92


🏁 Script executed:

# Check if there's any encryption implementation in server directory
fd -t f "encrypt\|cipher\|secret" src/server/

Repository: positonic/exponential

Length of output: 47


🏁 Script executed:

# Look for environment-based secrets or key management patterns
rg "process\.env\|SECRET|API_KEY" src/server/api/routers/mastra.ts -A 2 -B 2

Repository: positonic/exponential

Length of output: 713


🏁 Script executed:

# Search with correct ts file type for encryption
rg -i "encrypt|decrypt|cipher" --type ts -A 2 -B 1

Repository: positonic/exponential

Length of output: 25251


🏁 Script executed:

# Find Prisma schema file to understand integrationCredential structure
find . -name "*.prisma" -o -name "schema.prisma"

Repository: positonic/exponential

Length of output: 87


🏁 Script executed:

# Check package.json for encryption libraries
cat package.json | grep -A 20 -B 5 "dependencies\|devDependencies" | head -50

Repository: positonic/exponential

Length of output: 1737


🏁 Script executed:

# Search for how credentials are retrieved/used to see if decryption is expected
rg "integrationCredential" --type ts -B 2 -A 2 | head -60

Repository: positonic/exponential

Length of output: 3749


🏁 Script executed:

# Check if there's any crypto or encryption usage in the codebase
rg "require.*crypto|import.*crypto|require.*bcrypt|import.*bcrypt" --type ts

Repository: positonic/exponential

Length of output: 1118


🏁 Script executed:

# Search for credential retrieval patterns in mastra.ts
rg "integrationCredential" src/server/api/routers/mastra.ts -B 3 -A 3 | head -80

Repository: positonic/exponential

Length of output: 231


🏁 Script executed:

# Check the Prisma schema for integrationCredential structure
grep -A 20 "model IntegrationCredential" prisma/schema.prisma

Repository: positonic/exponential

Length of output: 795


🏁 Script executed:

# Look for how fireflies credentials are used
rg -i "fireflies" src/server/api/routers/mastra.ts -B 2 -A 5 | head -100

Repository: positonic/exponential

Length of output: 3358


Fireflies API key stored without encryption—use the existing encryption infrastructure.

The Fireflies API key is stored with isEncrypted: false, storing it as plaintext in the database. While the TODO acknowledges this gap, the codebase already has production-ready AES-256-GCM encryption (via src/server/utils/encryption.ts) that's actively used for other sensitive data like PII in contacts. The database schema itself defaults to isEncrypted: true, and other integrations like Slack and Notion correctly set this to true.

Either remove the explicit isEncrypted: false flag to use the default, or encrypt the key using encryptString() before storage. The same issue exists in multiple other integration endpoints (e.g., integration.ts, projectWorkflow.ts), so consider a broader audit.

🤖 Prompt for AI Agents
In `@src/server/api/routers/mastra.ts` around lines 1446 - 1454, The Fireflies
integration is saving the API key as plaintext by passing isEncrypted: false to
ctx.db.integrationCredential.create; replace this with the existing encryption
flow: call encryptString(...) from src/server/utils/encryption.ts on
input.apiKey, store the encrypted value as the key, and set isEncrypted: true
(or simply remove the explicit isEncrypted flag to rely on the DB default),
updating the create call in mastra.ts (ctx.db.integrationCredential.create)
accordingly; apply the same fix to other endpoints that set isEncrypted: false
(e.g., integration.ts, projectWorkflow.ts).

@positonic positonic merged commit a30b762 into main Jan 14, 2026
1 of 3 checks passed
Copy link
Copy Markdown

@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: 0

Caution

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

⚠️ Outside diff range comments (5)
src/server/services/github-integration.ts (1)

36-48: Critical: Missing decryption when retrieving access token.

The getAccessToken method returns tokenCredential.key directly without decrypting it. Since tokens are now stored encrypted, this will return the base64-encoded ciphertext instead of the actual token, causing all GitHub API requests to fail with authentication errors.

Proposed fix
+import { encryptCredential, getDecryptedKey } from "~/server/utils/credentialHelper";
-import { encryptCredential } from "~/server/utils/credentialHelper";

 private async getAccessToken(
   integration: GitHubIntegration,
 ): Promise<string> {
   const tokenCredential = integration.credentials.find(
     (cred) => cred.keyType === "access_token",
   );

   if (!tokenCredential) {
     throw new Error("GitHub access token not found");
   }

-  return tokenCredential.key;
+  const decryptedKey = getDecryptedKey(tokenCredential);
+  if (!decryptedKey) {
+    throw new Error("Failed to decrypt GitHub access token");
+  }
+  return decryptedKey;
 }
src/server/services/notion-integration.ts (1)

28-38: Critical: Missing decryption when retrieving access token.

Same issue as in github-integration.ts — the getAccessToken method returns tokenCredential.key directly without decrypting it. This will cause all Notion API requests to fail with authentication errors since the encrypted ciphertext will be sent instead of the actual token.

Proposed fix
+import { encryptCredential, getDecryptedKey } from '~/server/utils/credentialHelper';
-import { encryptCredential } from '~/server/utils/credentialHelper';

 private async getAccessToken(integration: NotionIntegration): Promise<string> {
   const tokenCredential = integration.credentials.find(
     cred => cred.keyType === 'access_token'
   );
   
   if (!tokenCredential) {
     throw new Error('Notion access token not found');
   }

-  return tokenCredential.key;
+  const decryptedKey = getDecryptedKey(tokenCredential);
+  if (!decryptedKey) {
+    throw new Error('Failed to decrypt Notion access token');
+  }
+  return decryptedKey;
 }
src/server/api/routers/integration.ts (3)

1782-1800: Critical: Missing decryption when using stored credentials.

The testConnection procedure retrieves credentials and uses them directly without decryption. Since credentials are now encrypted before storage, this will cause all connection tests to fail with invalid/garbled API keys.

The same issue affects:

  • Fireflies (line 1794)
  • Slack (line 1813)
  • Notion (line 1833)
  • Monday.com (lines 1866, 1876)
🔧 Proposed fix using getDecryptedKey helper

Import the decryption helper at the top of the file:

-import { encryptCredential } from "~/server/utils/credentialHelper";
+import { encryptCredential, getDecryptedKey } from "~/server/utils/credentialHelper";

Then decrypt credentials before use (example for Fireflies):

       const apiKeyCredential = integration.credentials.find(
         (c) => c.keyType === "API_KEY",
       );
       if (!apiKeyCredential) {
         throw new TRPCError({
           code: "NOT_FOUND",
           message: "No API key found for this integration",
         });
       }

-      const result = await testFirefliesConnection(apiKeyCredential.key);
+      const decryptedKey = getDecryptedKey(apiKeyCredential);
+      if (!decryptedKey) {
+        throw new TRPCError({
+          code: "INTERNAL_SERVER_ERROR",
+          message: "Failed to decrypt API key",
+        });
+      }
+      const result = await testFirefliesConnection(decryptedKey);

Apply similar changes for Slack, Notion, and Monday.com credential usage.


1954-1956: Critical: Missing decryption in refreshSlackIntegration.

The botTokenCredential.key is used directly without decryption. This will break Slack connection testing for integrations with encrypted bot tokens.

🔧 Proposed fix
+      const decryptedBotToken = getDecryptedKey(botTokenCredential);
+      if (!decryptedBotToken) {
+        throw new TRPCError({
+          code: "INTERNAL_SERVER_ERROR",
+          message: "Failed to decrypt bot token",
+        });
+      }
+
       // Test the connection and get latest team info
-      const testResult = await testSlackConnection(botTokenCredential.key);
+      const testResult = await testSlackConnection(decryptedBotToken);

2060-2065: Critical: getFirefliesApiKey returns encrypted value without decryption.

This procedure returns the raw credential key which may be encrypted, causing downstream consumers (webhook handlers) to receive garbled API keys.

🔧 Proposed fix
       if (!integration || integration.credentials.length === 0) {
         return null;
       }

-      return integration.credentials[0]!.key;
+      const credential = integration.credentials[0]!;
+      return getDecryptedKey(credential);
     }),
🧹 Nitpick comments (2)
src/server/utils/encryption.ts (1)

55-68: Misleading comment about legacy plaintext fallback.

The comment on line 65 suggests returning null allows for "legacy plaintext fallback," but this function actually returns null on failure rather than returning the original base64Encrypted value as plaintext. The actual fallback logic is handled in credentialHelper.ts via the isEncrypted flag.

Consider clarifying the comment:

Suggested comment update
   } catch {
-    // If decryption fails, the value might be stored in plaintext (legacy)
+    // Decryption failed - caller should handle fallback based on isEncrypted flag
     return null;
   }
src/server/utils/credentialHelper.ts (1)

31-49: Redundant try-catch wrapper.

The decryptFromBase64 function already catches exceptions internally and returns null on failure. The outer try-catch here will never catch anything thrown from decryptFromBase64. While this doesn't cause bugs, it adds unnecessary complexity.

Suggested simplification
 export function decryptCredential(key: string, isEncrypted: boolean): string | null {
   if (!isEncrypted) {
     // Plaintext credential (legacy or encryption unavailable)
     return key;
   }

-  try {
-    const decrypted = decryptFromBase64(key);
-    if (decrypted === null) {
-      // Decryption failed - might be corrupted or wrong key
-      console.error('Failed to decrypt credential - returning null');
-      return null;
-    }
-    return decrypted;
-  } catch (error) {
-    console.error('Credential decryption error:', error);
+  const decrypted = decryptFromBase64(key);
+  if (decrypted === null) {
+    console.error('Failed to decrypt credential - returning null');
     return null;
   }
+  return decrypted;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77753b6 and 41cf473.

📒 Files selected for processing (7)
  • src/server/api/routers/integration.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/services/FirefliesSyncService.ts
  • src/server/services/github-integration.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/utils/encryption.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{css,scss,sass,pcss,ts,tsx,jsx,js}: NEVER use hardcoded hex colors (#123456, #fff, etc.)
NEVER use RGB/RGBA colors (rgb(), rgba())
ALWAYS use Tailwind classes or CSS variables for colors

Files:

  • src/server/services/github-integration.ts
  • src/server/utils/encryption.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
  • src/server/services/FirefliesSyncService.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Add JSDoc/documentation comments for public functions
Include example usage in documentation/comments where appropriate
Explain non-obvious implementation details in comments
Use the function keyword for pure functions
Avoid unnecessary curly braces in simple conditionals; prefer concise syntax

**/*.{ts,tsx,js,jsx}: Use nullish coalescing operator (??) instead of logical OR (||) operator
All promises must be awaited or explicitly handled with .catch() or .then()
Only await actual promises, not non-promise values
Do not have unused variables or imports
Use descriptive variable names with auxiliary verbs like isLoading, hasError, canSubmit

Files:

  • src/server/services/github-integration.ts
  • src/server/utils/encryption.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
  • src/server/services/FirefliesSyncService.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Verify import/export syntax compatibility (ESM)
Check for proper TypeScript types throughout the codebase
Implement proper type definitions for variables, functions, and return types
Avoid type assertions unless necessary
Use proper generic constraints on TypeScript generics
Use proper TypeScript types for all variables, functions, and return types
Prefer interfaces over type aliases for object shapes
Avoid enums; use maps/objects instead
Use 'nuqs' for URL search parameter state management

**/*.{ts,tsx}: Use Tailwind CSS classes and CSS variables for styling instead of inline styles with hardcoded colors
Do not use any types - use proper TypeScript types instead
Use proper type imports with import type {} syntax for type-only imports
Prefer interfaces over types for defining object shapes in TypeScript
Use Mantine v7 components with Tailwind CSS for styling
Prefer Next.js Link over useRouter.push() for navigation
Use component={Link} with Mantine components for navigation instead of separate Link wrappers
Mantine components should use theme configuration from /src/styles/mantineTheme.ts

Files:

  • src/server/services/github-integration.ts
  • src/server/utils/encryption.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
  • src/server/services/FirefliesSyncService.ts
**/*.{ts,tsx,css,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,css,js,jsx}: NEVER USE HARDCODED HEX COLORS like #262626, #C1C2C5, #373A40, #1a1b1e. Use Tailwind classes (e.g., bg-background-primary) or CSS variables (var(--background-primary)) instead
NEVER USE RGB/RGBA COLORS like rgb(26, 27, 30) or rgba(0, 0, 0, 0.5). Use Tailwind classes or CSS variables instead
Use semantic color names for different UI elements (e.g., bg-background-primary, text-text-primary, border-border-primary)
Use CSS variables from /src/styles/globals.css configured for theme switching

Files:

  • src/server/services/github-integration.ts
  • src/server/utils/encryption.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
  • src/server/services/FirefliesSyncService.ts
src/server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prisma with proper error handling for database operations

Files:

  • src/server/services/github-integration.ts
  • src/server/utils/encryption.ts
  • src/server/services/notion-integration.ts
  • src/server/utils/credentialHelper.ts
  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
  • src/server/services/FirefliesSyncService.ts
src/server/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/server/api/**/*.{ts,tsx}: All API logic must use tRPC for end-to-end type safety instead of other API patterns
Use authentication middleware to protect authenticated endpoints in tRPC routers

Files:

  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
src/server/api/routers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Accept optional workspaceId in tRPC router inputs and filter database queries accordingly

Files:

  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
🧠 Learnings (3)
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/**/*.{ts,tsx} : Use Prisma with proper error handling for database operations

Applied to files:

  • src/server/services/notion-integration.ts
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/**/*.{ts,tsx} : Use authentication middleware to protect authenticated endpoints in tRPC routers

Applied to files:

  • src/server/api/routers/projectWorkflow.ts
  • src/server/api/routers/integration.ts
📚 Learning: 2026-01-13T14:19:07.763Z
Learnt from: CR
Repo: positonic/exponential PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T14:19:07.763Z
Learning: Applies to src/server/api/routers/**/*.{ts,tsx} : Accept optional workspaceId in tRPC router inputs and filter database queries accordingly

Applied to files:

  • src/server/api/routers/projectWorkflow.ts
🧬 Code graph analysis (6)
src/server/services/github-integration.ts (2)
src/server/utils/credentialHelper.ts (1)
  • encryptCredential (12-25)
src/server/db.ts (1)
  • db (14-14)
src/server/services/notion-integration.ts (2)
src/server/utils/credentialHelper.ts (1)
  • encryptCredential (12-25)
src/server/db.ts (1)
  • db (14-14)
src/server/utils/credentialHelper.ts (1)
src/server/utils/encryption.ts (3)
  • isEncryptionAvailable (74-76)
  • encryptToBase64 (50-53)
  • decryptFromBase64 (59-68)
src/server/api/routers/projectWorkflow.ts (1)
src/server/utils/credentialHelper.ts (1)
  • encryptCredential (12-25)
src/server/api/routers/integration.ts (1)
src/server/utils/credentialHelper.ts (1)
  • encryptCredential (12-25)
src/server/services/FirefliesSyncService.ts (1)
src/server/utils/credentialHelper.ts (1)
  • getDecryptedKey (55-57)
🔇 Additional comments (15)
src/server/utils/encryption.ts (2)

46-53: LGTM!

Clean wrapper function with appropriate JSDoc documentation. Correctly delegates to the existing encryptString utility.


70-76: LGTM!

Simple utility to check encryption availability. Correctly returns a boolean based on the presence of KEY_ENV.

src/server/utils/credentialHelper.ts (3)

1-7: LGTM!

Good module-level documentation explaining the purpose and backward compatibility strategy for credential handling.


12-25: LGTM!

Solid implementation with appropriate fallback behavior when encryption is unavailable. The error logging provides visibility for debugging encryption failures.


51-57: LGTM!

Clean convenience wrapper for Prisma credential objects.

src/server/services/github-integration.ts (1)

8-8: Encryption applied correctly for storage.

The access token is now properly encrypted before being stored in the database.

Also applies to: 109-117

src/server/services/FirefliesSyncService.ts (2)

3-3: LGTM!

Correct implementation of credential decryption. The getDecryptedKey call properly handles both encrypted and plaintext credentials, with appropriate error handling and logging.

Also applies to: 54-59


35-38: Team-based access expansion is consistent with the codebase pattern.

The OR condition allowing access via direct ownership or team membership is used consistently for shared resources. Similar patterns appear in SlackChannelResolver (line 128) and TranscriptionProcessingService (line 434) for project queries. This is an appropriate authorization model for team-based integration access.

src/server/services/notion-integration.ts (1)

3-3: Encryption applied correctly for storage.

The Notion access token is properly encrypted before being persisted to the database.

Also applies to: 102-110

src/server/api/routers/integration.ts (4)

10-10: LGTM!

Import of encryptCredential helper is correctly added for credential encryption support.


498-507: LGTM - encryption properly applied to API keys.

The credential encryption implementation is correct—sensitive API keys are encrypted before storage with the isEncrypted flag properly set from the encryption result.


634-677: Good classification of sensitive vs non-sensitive credentials.

The implementation correctly:

  • Encrypts sensitive values: BOT_TOKEN, SIGNING_SECRET, USER_TOKEN
  • Stores identifiers in plaintext: TEAM_ID, APP_ID

779-809: LGTM - WhatsApp credential encryption correctly implemented.

Properly encrypts sensitive tokens (ACCESS_TOKEN, WEBHOOK_VERIFY_TOKEN) while leaving identifiers (PHONE_NUMBER_ID, BUSINESS_ACCOUNT_ID) in plaintext.

src/server/api/routers/projectWorkflow.ts (2)

7-7: LGTM!

Import correctly added for credential encryption support.


449-458: LGTM - credential encryption properly implemented.

The API key is correctly encrypted before storage using encryptCredential, with the isEncrypted flag properly propagated from the encryption result.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@coderabbitai coderabbitai Bot mentioned this pull request Jan 15, 2026
18 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Apr 28, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant