Skip to content

Commit 560d184

Browse files
authored
v0.3.7: ms teams webhook, docker fixes, condition block dropdown, fixed routing with workflow block
2 parents f2b1c73 + e4fbb67 commit 560d184

File tree

35 files changed

+1222
-154
lines changed

35 files changed

+1222
-154
lines changed

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ docker compose -f docker-compose.prod.yml up -d
9191

9292
### Option 4: Manual Setup
9393

94+
**Requirements:**
95+
- [Bun](https://bun.sh/) runtime
96+
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
97+
98+
**Note:** Sim Studio uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.
99+
94100
1. Clone and install dependencies:
95101

96102
```bash
@@ -99,20 +105,43 @@ cd sim
99105
bun install
100106
```
101107

102-
2. Set up environment:
108+
2. Set up PostgreSQL with pgvector:
109+
110+
You need PostgreSQL with the `vector` extension for embedding support. Choose one option:
111+
112+
**Option A: Using Docker (Recommended)**
113+
```bash
114+
# Start PostgreSQL with pgvector extension
115+
docker run --name simstudio-db \
116+
-e POSTGRES_PASSWORD=your_password \
117+
-e POSTGRES_DB=simstudio \
118+
-p 5432:5432 -d \
119+
pgvector/pgvector:pg17
120+
```
121+
122+
**Option B: Manual Installation**
123+
- Install PostgreSQL 12+ and the pgvector extension
124+
- See [pgvector installation guide](https://github.com/pgvector/pgvector#installation)
125+
126+
3. Set up environment:
103127

104128
```bash
105129
cd apps/sim
106130
cp .env.example .env # Configure with required variables (DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL)
107131
```
108132

109-
3. Set up the database:
133+
Update your `.env` file with the database URL:
134+
```bash
135+
DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
136+
```
137+
138+
4. Set up the database:
110139

111140
```bash
112-
bunx drizzle-kit push
141+
bunx drizzle-kit migrate
113142
```
114143

115-
4. Start the development servers:
144+
5. Start the development servers:
116145

117146
**Recommended approach - run both servers together (from project root):**
118147

apps/sim/app/api/tools/confluence/pages/route.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NextResponse } from 'next/server'
2+
import { createLogger } from '@/lib/logs/console-logger'
23
import { getConfluenceCloudId } from '@/tools/confluence/utils'
34

5+
const logger = createLogger('ConfluencePages')
6+
47
export const dynamic = 'force-dynamic'
58

69
export async function POST(request: Request) {
@@ -39,7 +42,7 @@ export async function POST(request: Request) {
3942
const queryString = queryParams.toString()
4043
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl
4144

42-
console.log(`Fetching Confluence pages from: ${url}`)
45+
logger.info(`Fetching Confluence pages from: ${url}`)
4346

4447
// Make the request to Confluence API with OAuth Bearer token
4548
const response = await fetch(url, {
@@ -50,23 +53,23 @@ export async function POST(request: Request) {
5053
},
5154
})
5255

53-
console.log('Response status:', response.status, response.statusText)
56+
logger.info('Response status:', response.status, response.statusText)
5457

5558
if (!response.ok) {
56-
console.error(`Confluence API error: ${response.status} ${response.statusText}`)
59+
logger.error(`Confluence API error: ${response.status} ${response.statusText}`)
5760
let errorMessage
5861

5962
try {
6063
const errorData = await response.json()
61-
console.error('Error details:', JSON.stringify(errorData, null, 2))
64+
logger.error('Error details:', JSON.stringify(errorData, null, 2))
6265
errorMessage = errorData.message || `Failed to fetch Confluence pages (${response.status})`
6366
} catch (e) {
64-
console.error('Could not parse error response as JSON:', e)
67+
logger.error('Could not parse error response as JSON:', e)
6568

6669
// Try to get the response text for more context
6770
try {
6871
const text = await response.text()
69-
console.error('Response text:', text)
72+
logger.error('Response text:', text)
7073
errorMessage = `Failed to fetch Confluence pages: ${response.status} ${response.statusText}`
7174
} catch (_textError) {
7275
errorMessage = `Failed to fetch Confluence pages: ${response.status} ${response.statusText}`
@@ -77,13 +80,13 @@ export async function POST(request: Request) {
7780
}
7881

7982
const data = await response.json()
80-
console.log('Confluence API response:', `${JSON.stringify(data, null, 2).substring(0, 300)}...`)
81-
console.log(`Found ${data.results?.length || 0} pages`)
83+
logger.info('Confluence API response:', `${JSON.stringify(data, null, 2).substring(0, 300)}...`)
84+
logger.info(`Found ${data.results?.length || 0} pages`)
8285

8386
if (data.results && data.results.length > 0) {
84-
console.log('First few pages:')
87+
logger.info('First few pages:')
8588
for (const page of data.results.slice(0, 3)) {
86-
console.log(`- ${page.id}: ${page.title}`)
89+
logger.info(`- ${page.id}: ${page.title}`)
8790
}
8891
}
8992

@@ -99,7 +102,7 @@ export async function POST(request: Request) {
99102
})),
100103
})
101104
} catch (error) {
102-
console.error('Error fetching Confluence pages:', error)
105+
logger.error('Error fetching Confluence pages:', error)
103106
return NextResponse.json(
104107
{ error: (error as Error).message || 'Internal server error' },
105108
{ status: 500 }

apps/sim/app/api/webhooks/test/route.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,58 @@ export async function GET(request: NextRequest) {
465465
})
466466
}
467467

468+
case 'microsoftteams': {
469+
const hmacSecret = providerConfig.hmacSecret
470+
471+
if (!hmacSecret) {
472+
logger.warn(`[${requestId}] Microsoft Teams webhook missing HMAC secret: ${webhookId}`)
473+
return NextResponse.json(
474+
{ success: false, error: 'Microsoft Teams webhook requires HMAC secret' },
475+
{ status: 400 }
476+
)
477+
}
478+
479+
logger.info(`[${requestId}] Microsoft Teams webhook test successful: ${webhookId}`)
480+
return NextResponse.json({
481+
success: true,
482+
webhook: {
483+
id: foundWebhook.id,
484+
url: webhookUrl,
485+
isActive: foundWebhook.isActive,
486+
},
487+
message: 'Microsoft Teams outgoing webhook configuration is valid.',
488+
setup: {
489+
url: webhookUrl,
490+
hmacSecretConfigured: !!hmacSecret,
491+
instructions: [
492+
'Create an outgoing webhook in Microsoft Teams',
493+
'Set the callback URL to the webhook URL above',
494+
'Copy the HMAC security token to the configuration',
495+
'Users can trigger the webhook by @mentioning it in Teams',
496+
],
497+
},
498+
test: {
499+
curlCommand: `curl -X POST "${webhookUrl}" \\
500+
-H "Content-Type: application/json" \\
501+
-H "Authorization: HMAC <signature>" \\
502+
-d '{"type":"message","text":"Hello from Microsoft Teams!","from":{"id":"test","name":"Test User"}}'`,
503+
samplePayload: {
504+
type: 'message',
505+
id: '1234567890',
506+
timestamp: new Date().toISOString(),
507+
text: 'Hello Sim Studio Bot!',
508+
from: {
509+
id: '29:1234567890abcdef',
510+
name: 'Test User',
511+
},
512+
conversation: {
513+
id: '19:meeting_abcdef@thread.v2',
514+
},
515+
},
516+
},
517+
})
518+
}
519+
468520
default: {
469521
// Generic webhook test
470522
logger.info(`[${requestId}] Generic webhook test successful: ${webhookId}`)

apps/sim/app/api/webhooks/trigger/[path]/route.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
processGenericDeduplication,
1212
processWebhook,
1313
processWhatsAppDeduplication,
14+
validateMicrosoftTeamsSignature,
1415
} from '@/lib/webhooks/utils'
1516
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
1617
import { db } from '@/db'
@@ -243,6 +244,51 @@ export async function POST(
243244
return slackChallengeResponse
244245
}
245246

247+
// Handle Microsoft Teams outgoing webhook signature verification (must be done before timeout)
248+
if (foundWebhook.provider === 'microsoftteams') {
249+
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
250+
251+
if (providerConfig.hmacSecret) {
252+
const authHeader = request.headers.get('authorization')
253+
254+
if (!authHeader || !authHeader.startsWith('HMAC ')) {
255+
logger.warn(
256+
`[${requestId}] Microsoft Teams outgoing webhook missing HMAC authorization header`
257+
)
258+
return new NextResponse('Unauthorized - Missing HMAC signature', { status: 401 })
259+
}
260+
261+
// Get the raw body for HMAC verification
262+
const rawBody = await request.text()
263+
264+
const isValidSignature = validateMicrosoftTeamsSignature(
265+
providerConfig.hmacSecret,
266+
authHeader,
267+
rawBody
268+
)
269+
270+
if (!isValidSignature) {
271+
logger.warn(`[${requestId}] Microsoft Teams HMAC signature verification failed`)
272+
return new NextResponse('Unauthorized - Invalid HMAC signature', { status: 401 })
273+
}
274+
275+
logger.debug(`[${requestId}] Microsoft Teams HMAC signature verified successfully`)
276+
277+
// Parse the body again since we consumed it for verification
278+
try {
279+
body = JSON.parse(rawBody)
280+
} catch (parseError) {
281+
logger.error(
282+
`[${requestId}] Failed to parse Microsoft Teams webhook body after verification`,
283+
{
284+
error: parseError instanceof Error ? parseError.message : String(parseError),
285+
}
286+
)
287+
return new NextResponse('Invalid JSON payload', { status: 400 })
288+
}
289+
}
290+
}
291+
246292
// Skip processing if another instance is already handling this request
247293
if (!hasExecutionLock) {
248294
logger.info(`[${requestId}] Skipping execution as lock was not acquired`)

apps/sim/app/api/workspaces/route.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import crypto from 'crypto'
22
import { and, desc, eq, isNull } from 'drizzle-orm'
33
import { NextResponse } from 'next/server'
44
import { getSession } from '@/lib/auth'
5+
import { createLogger } from '@/lib/logs/console-logger'
56
import { db } from '@/db'
67
import { permissions, workflow, workflowBlocks, workspace } from '@/db/schema'
78

9+
const logger = createLogger('Workspaces')
10+
811
// Get all workspaces for the current user
912
export async function GET() {
1013
const session = await getSession()
@@ -244,12 +247,12 @@ async function createWorkspace(userId: string, name: string) {
244247
updatedAt: now,
245248
})
246249

247-
console.log(
248-
`Created workspace ${workspaceId} with initial workflow ${workflowId} for user ${userId}`
250+
logger.info(
251+
`Created workspace ${workspaceId} with initial workflow ${workflowId} for user ${userId}`
249252
)
250253
})
251254
} catch (error) {
252-
console.error(`Failed to create workspace ${workspaceId} with initial workflow:`, error)
255+
logger.error(`Failed to create workspace ${workspaceId} with initial workflow:`, error)
253256
throw error
254257
}
255258

@@ -276,7 +279,7 @@ async function migrateExistingWorkflows(userId: string, workspaceId: string) {
276279
return // No orphaned workflows to migrate
277280
}
278281

279-
console.log(
282+
logger.info(
280283
`Migrating ${orphanedWorkflows.length} workflows to workspace ${workspaceId} for user ${userId}`
281284
)
282285

@@ -308,6 +311,6 @@ async function ensureWorkflowsHaveWorkspace(userId: string, defaultWorkspaceId:
308311
})
309312
.where(and(eq(workflow.userId, userId), isNull(workflow.workspaceId)))
310313

311-
console.log(`Fixed ${orphanedWorkflows.length} orphaned workflows for user ${userId}`)
314+
logger.info(`Fixed ${orphanedWorkflows.length} orphaned workflows for user ${userId}`)
312315
}
313316
}

apps/sim/app/workspace/[workspaceId]/templates/[id]/page.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { and, eq } from 'drizzle-orm'
22
import { notFound } from 'next/navigation'
33
import { getSession } from '@/lib/auth'
4+
import { createLogger } from '@/lib/logs/console-logger'
45
import { db } from '@/db'
56
import { templateStars, templates } from '@/db/schema'
67
import type { Template } from '../templates'
78
import TemplateDetails from './template'
89

10+
const logger = createLogger('TemplatePage')
11+
912
interface TemplatePageProps {
1013
params: Promise<{
1114
workspaceId: string
@@ -58,7 +61,7 @@ export default async function TemplatePage({ params }: TemplatePageProps) {
5861

5962
// Validate that required fields are present
6063
if (!template.id || !template.name || !template.author) {
61-
console.error('Template missing required fields:', {
64+
logger.error('Template missing required fields:', {
6265
id: template.id,
6366
name: template.name,
6467
author: template.author,
@@ -100,9 +103,9 @@ export default async function TemplatePage({ params }: TemplatePageProps) {
100103
isStarred,
101104
}
102105

103-
console.log('Template from DB:', template)
104-
console.log('Serialized template:', serializedTemplate)
105-
console.log('Template state from DB:', template.state)
106+
logger.info('Template from DB:', template)
107+
logger.info('Serialized template:', serializedTemplate)
108+
logger.info('Template state from DB:', template.state)
106109

107110
return (
108111
<TemplateDetails

apps/sim/app/workspace/[workspaceId]/templates/[id]/template.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export default function TemplateDetails({
143143
const renderWorkflowPreview = () => {
144144
// Follow the same pattern as deployed-workflow-card.tsx
145145
if (!template?.state) {
146-
console.log('Template has no state:', template)
146+
logger.info('Template has no state:', template)
147147
return (
148148
<div className='flex h-full items-center justify-center text-center'>
149149
<div className='text-muted-foreground'>
@@ -154,10 +154,10 @@ export default function TemplateDetails({
154154
)
155155
}
156156

157-
console.log('Template state:', template.state)
158-
console.log('Template state type:', typeof template.state)
159-
console.log('Template state blocks:', template.state.blocks)
160-
console.log('Template state edges:', template.state.edges)
157+
logger.info('Template state:', template.state)
158+
logger.info('Template state type:', typeof template.state)
159+
logger.info('Template state blocks:', template.state.blocks)
160+
logger.info('Template state edges:', template.state.edges)
161161

162162
try {
163163
return (

apps/sim/app/workspace/[workspaceId]/templates/templates.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export default function Templates({ initialTemplates, currentUserId }: Templates
9292

9393
const handleCreateNew = () => {
9494
// TODO: Open create template modal or navigate to create page
95-
console.log('Create new template')
95+
logger.info('Create new template')
9696
}
9797

9898
// Handle star change callback from template card

0 commit comments

Comments
 (0)