mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
feat(tools): add MongoDB (#1225)
* added mongo, haven't tested * fixed bugs, refined prompts, added billing for wand if billing enabled * add docs * ack PR comments
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
"microsoft_planner",
|
||||
"microsoft_teams",
|
||||
"mistral_parse",
|
||||
"mongodb",
|
||||
"mysql",
|
||||
"notion",
|
||||
"onedrive",
|
||||
|
||||
264
apps/docs/content/docs/tools/mongodb.mdx
Normal file
264
apps/docs/content/docs/tools/mongodb.mdx
Normal file
@@ -0,0 +1,264 @@
|
||||
---
|
||||
title: MongoDB
|
||||
description: Connect to MongoDB database
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="mongodb"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='currentColor'
|
||||
d='M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#45A538'
|
||||
d='M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#46A037'
|
||||
d='M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#409433'
|
||||
d='M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4FAA41'
|
||||
d='M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4AA73C'
|
||||
d='M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#57AE47'
|
||||
d='M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#60B24F'
|
||||
d='M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#A9AA88'
|
||||
d='M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#B6B598'
|
||||
d='M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#C2C1A7'
|
||||
d='M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#CECDB7'
|
||||
d='M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#DBDAC7'
|
||||
d='M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#EBE9DC'
|
||||
d='M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#CECDB7'
|
||||
d='M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4FAA41'
|
||||
d='M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Connect to any MongoDB database to execute queries, manage data, and perform database operations. Supports find, insert, update, delete, and aggregation operations with secure connection handling.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `mongodb_query`
|
||||
|
||||
Execute find operation on MongoDB collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | MongoDB server hostname or IP address |
|
||||
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
|
||||
| `database` | string | Yes | Database name to connect to |
|
||||
| `username` | string | No | MongoDB username |
|
||||
| `password` | string | No | MongoDB password |
|
||||
| `authSource` | string | No | Authentication database |
|
||||
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
|
||||
| `collection` | string | Yes | Collection name to query |
|
||||
| `query` | string | No | MongoDB query filter as JSON string |
|
||||
| `limit` | number | No | Maximum number of documents to return |
|
||||
| `sort` | string | No | Sort criteria as JSON string |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `documents` | array | Array of documents returned from the query |
|
||||
| `documentCount` | number | Number of documents returned |
|
||||
|
||||
### `mongodb_insert`
|
||||
|
||||
Insert documents into MongoDB collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | MongoDB server hostname or IP address |
|
||||
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
|
||||
| `database` | string | Yes | Database name to connect to |
|
||||
| `username` | string | No | MongoDB username |
|
||||
| `password` | string | No | MongoDB password |
|
||||
| `authSource` | string | No | Authentication database |
|
||||
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
|
||||
| `collection` | string | Yes | Collection name to insert into |
|
||||
| `documents` | array | Yes | Array of documents to insert |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `documentCount` | number | Number of documents inserted |
|
||||
| `insertedId` | string | ID of inserted document \(single insert\) |
|
||||
| `insertedIds` | array | Array of inserted document IDs \(multiple insert\) |
|
||||
|
||||
### `mongodb_update`
|
||||
|
||||
Update documents in MongoDB collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | MongoDB server hostname or IP address |
|
||||
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
|
||||
| `database` | string | Yes | Database name to connect to |
|
||||
| `username` | string | No | MongoDB username |
|
||||
| `password` | string | No | MongoDB password |
|
||||
| `authSource` | string | No | Authentication database |
|
||||
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
|
||||
| `collection` | string | Yes | Collection name to update |
|
||||
| `filter` | string | Yes | Filter criteria as JSON string |
|
||||
| `update` | string | Yes | Update operations as JSON string |
|
||||
| `upsert` | boolean | No | Create document if not found |
|
||||
| `multi` | boolean | No | Update multiple documents |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `matchedCount` | number | Number of documents matched by filter |
|
||||
| `modifiedCount` | number | Number of documents modified |
|
||||
| `documentCount` | number | Total number of documents affected |
|
||||
| `insertedId` | string | ID of inserted document \(if upsert\) |
|
||||
|
||||
### `mongodb_delete`
|
||||
|
||||
Delete documents from MongoDB collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | MongoDB server hostname or IP address |
|
||||
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
|
||||
| `database` | string | Yes | Database name to connect to |
|
||||
| `username` | string | No | MongoDB username |
|
||||
| `password` | string | No | MongoDB password |
|
||||
| `authSource` | string | No | Authentication database |
|
||||
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
|
||||
| `collection` | string | Yes | Collection name to delete from |
|
||||
| `filter` | string | Yes | Filter criteria as JSON string |
|
||||
| `multi` | boolean | No | Delete multiple documents |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `deletedCount` | number | Number of documents deleted |
|
||||
| `documentCount` | number | Total number of documents affected |
|
||||
|
||||
### `mongodb_execute`
|
||||
|
||||
Execute MongoDB aggregation pipeline
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | MongoDB server hostname or IP address |
|
||||
| `port` | number | Yes | MongoDB server port \(default: 27017\) |
|
||||
| `database` | string | Yes | Database name to connect to |
|
||||
| `username` | string | No | MongoDB username |
|
||||
| `password` | string | No | MongoDB password |
|
||||
| `authSource` | string | No | Authentication database |
|
||||
| `ssl` | string | No | SSL connection mode \(disabled, required, preferred\) |
|
||||
| `collection` | string | Yes | Collection name to execute pipeline on |
|
||||
| `pipeline` | string | Yes | Aggregation pipeline as JSON string |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `documents` | array | Array of documents returned from aggregation |
|
||||
| `documentCount` | number | Number of documents returned |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `mongodb`
|
||||
114
apps/sim/app/api/tools/mongodb/delete/route.ts
Normal file
114
apps/sim/app/api/tools/mongodb/delete/route.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
|
||||
|
||||
const logger = createLogger('MongoDBDeleteAPI')
|
||||
|
||||
const DeleteSchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.coerce.number().int().positive('Port must be a positive integer'),
|
||||
database: z.string().min(1, 'Database name is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
authSource: z.string().optional(),
|
||||
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
|
||||
collection: z.string().min(1, 'Collection name is required'),
|
||||
filter: z
|
||||
.union([z.string(), z.object({}).passthrough()])
|
||||
.transform((val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val
|
||||
})
|
||||
.refine((val) => val && val.trim() !== '' && val !== '{}', {
|
||||
message: 'Filter is required for MongoDB Delete',
|
||||
}),
|
||||
multi: z
|
||||
.union([z.boolean(), z.string(), z.undefined()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val === 'true' || val === true) return true
|
||||
if (val === 'false' || val === false) return false
|
||||
return false // Default to false
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
let client = null
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = DeleteSchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Deleting document(s) from ${params.host}:${params.port}/${params.database}.${params.collection} (multi: ${params.multi})`
|
||||
)
|
||||
|
||||
const sanitizedCollection = sanitizeCollectionName(params.collection)
|
||||
|
||||
const filterValidation = validateFilter(params.filter)
|
||||
if (!filterValidation.isValid) {
|
||||
logger.warn(`[${requestId}] Filter validation failed: ${filterValidation.error}`)
|
||||
return NextResponse.json(
|
||||
{ error: `Filter validation failed: ${filterValidation.error}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let filterDoc
|
||||
try {
|
||||
filterDoc = JSON.parse(params.filter)
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Invalid filter JSON: ${params.filter}`)
|
||||
return NextResponse.json({ error: 'Invalid JSON format in filter' }, { status: 400 })
|
||||
}
|
||||
|
||||
client = await createMongoDBConnection({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl,
|
||||
})
|
||||
|
||||
const db = client.db(params.database)
|
||||
const coll = db.collection(sanitizedCollection)
|
||||
|
||||
let result
|
||||
if (params.multi) {
|
||||
result = await coll.deleteMany(filterDoc)
|
||||
} else {
|
||||
result = await coll.deleteOne(filterDoc)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Delete completed: ${result.deletedCount} documents deleted`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `${result.deletedCount} documents deleted`,
|
||||
deletedCount: result.deletedCount,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] MongoDB delete failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `MongoDB delete failed: ${errorMessage}` }, { status: 500 })
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
102
apps/sim/app/api/tools/mongodb/execute/route.ts
Normal file
102
apps/sim/app/api/tools/mongodb/execute/route.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createMongoDBConnection, sanitizeCollectionName, validatePipeline } from '../utils'
|
||||
|
||||
const logger = createLogger('MongoDBExecuteAPI')
|
||||
|
||||
const ExecuteSchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.coerce.number().int().positive('Port must be a positive integer'),
|
||||
database: z.string().min(1, 'Database name is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
authSource: z.string().optional(),
|
||||
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
|
||||
collection: z.string().min(1, 'Collection name is required'),
|
||||
pipeline: z
|
||||
.union([z.string(), z.array(z.object({}).passthrough())])
|
||||
.transform((val) => {
|
||||
if (Array.isArray(val)) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val
|
||||
})
|
||||
.refine((val) => val && val.trim() !== '', {
|
||||
message: 'Pipeline is required',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
let client = null
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = ExecuteSchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Executing aggregation pipeline on ${params.host}:${params.port}/${params.database}.${params.collection}`
|
||||
)
|
||||
|
||||
const sanitizedCollection = sanitizeCollectionName(params.collection)
|
||||
|
||||
const pipelineValidation = validatePipeline(params.pipeline)
|
||||
if (!pipelineValidation.isValid) {
|
||||
logger.warn(`[${requestId}] Pipeline validation failed: ${pipelineValidation.error}`)
|
||||
return NextResponse.json(
|
||||
{ error: `Pipeline validation failed: ${pipelineValidation.error}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const pipelineDoc = JSON.parse(params.pipeline)
|
||||
|
||||
client = await createMongoDBConnection({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl,
|
||||
})
|
||||
|
||||
const db = client.db(params.database)
|
||||
const coll = db.collection(sanitizedCollection)
|
||||
|
||||
const cursor = coll.aggregate(pipelineDoc)
|
||||
const documents = await cursor.toArray()
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Aggregation completed successfully, returned ${documents.length} documents`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Aggregation completed, returned ${documents.length} documents`,
|
||||
documents,
|
||||
documentCount: documents.length,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] MongoDB aggregation failed:`, error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: `MongoDB aggregation failed: ${errorMessage}` },
|
||||
{ status: 500 }
|
||||
)
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
98
apps/sim/app/api/tools/mongodb/insert/route.ts
Normal file
98
apps/sim/app/api/tools/mongodb/insert/route.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createMongoDBConnection, sanitizeCollectionName } from '../utils'
|
||||
|
||||
const logger = createLogger('MongoDBInsertAPI')
|
||||
|
||||
const InsertSchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.coerce.number().int().positive('Port must be a positive integer'),
|
||||
database: z.string().min(1, 'Database name is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
authSource: z.string().optional(),
|
||||
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
|
||||
collection: z.string().min(1, 'Collection name is required'),
|
||||
documents: z
|
||||
.union([z.array(z.record(z.unknown())), z.string()])
|
||||
.transform((val) => {
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(val)
|
||||
return Array.isArray(parsed) ? parsed : [parsed]
|
||||
} catch {
|
||||
throw new Error('Invalid JSON in documents field')
|
||||
}
|
||||
}
|
||||
return val
|
||||
})
|
||||
.refine((val) => Array.isArray(val) && val.length > 0, {
|
||||
message: 'At least one document is required',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
let client = null
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = InsertSchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Inserting ${params.documents.length} document(s) into ${params.host}:${params.port}/${params.database}.${params.collection}`
|
||||
)
|
||||
|
||||
const sanitizedCollection = sanitizeCollectionName(params.collection)
|
||||
client = await createMongoDBConnection({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl,
|
||||
})
|
||||
|
||||
const db = client.db(params.database)
|
||||
const coll = db.collection(sanitizedCollection)
|
||||
|
||||
let result
|
||||
if (params.documents.length === 1) {
|
||||
result = await coll.insertOne(params.documents[0] as Record<string, unknown>)
|
||||
logger.info(`[${requestId}] Single document inserted successfully`)
|
||||
return NextResponse.json({
|
||||
message: 'Document inserted successfully',
|
||||
insertedId: result.insertedId.toString(),
|
||||
documentCount: 1,
|
||||
})
|
||||
}
|
||||
result = await coll.insertMany(params.documents as Record<string, unknown>[])
|
||||
const insertedCount = Object.keys(result.insertedIds).length
|
||||
logger.info(`[${requestId}] ${insertedCount} documents inserted successfully`)
|
||||
return NextResponse.json({
|
||||
message: `${insertedCount} documents inserted successfully`,
|
||||
insertedIds: Object.values(result.insertedIds).map((id) => id.toString()),
|
||||
documentCount: insertedCount,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] MongoDB insert failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `MongoDB insert failed: ${errorMessage}` }, { status: 500 })
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
136
apps/sim/app/api/tools/mongodb/query/route.ts
Normal file
136
apps/sim/app/api/tools/mongodb/query/route.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
|
||||
|
||||
const logger = createLogger('MongoDBQueryAPI')
|
||||
|
||||
const QuerySchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.coerce.number().int().positive('Port must be a positive integer'),
|
||||
database: z.string().min(1, 'Database name is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
authSource: z.string().optional(),
|
||||
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
|
||||
collection: z.string().min(1, 'Collection name is required'),
|
||||
query: z
|
||||
.union([z.string(), z.object({}).passthrough()])
|
||||
.optional()
|
||||
.default('{}')
|
||||
.transform((val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val || '{}'
|
||||
}),
|
||||
limit: z
|
||||
.union([z.coerce.number().int().positive(), z.literal(''), z.undefined()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val === '' || val === undefined || val === null) {
|
||||
return 100
|
||||
}
|
||||
return val
|
||||
}),
|
||||
sort: z
|
||||
.union([z.string(), z.object({}).passthrough(), z.null()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
let client = null
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = QuerySchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Executing MongoDB query on ${params.host}:${params.port}/${params.database}.${params.collection}`
|
||||
)
|
||||
|
||||
const sanitizedCollection = sanitizeCollectionName(params.collection)
|
||||
|
||||
let filter = {}
|
||||
if (params.query?.trim()) {
|
||||
const validation = validateFilter(params.query)
|
||||
if (!validation.isValid) {
|
||||
logger.warn(`[${requestId}] Filter validation failed: ${validation.error}`)
|
||||
return NextResponse.json(
|
||||
{ error: `Filter validation failed: ${validation.error}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
filter = JSON.parse(params.query)
|
||||
}
|
||||
|
||||
let sortCriteria = {}
|
||||
if (params.sort?.trim()) {
|
||||
try {
|
||||
sortCriteria = JSON.parse(params.sort)
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Invalid sort JSON: ${params.sort}`)
|
||||
return NextResponse.json({ error: 'Invalid JSON format in sort criteria' }, { status: 400 })
|
||||
}
|
||||
}
|
||||
|
||||
client = await createMongoDBConnection({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl,
|
||||
})
|
||||
|
||||
const db = client.db(params.database)
|
||||
const coll = db.collection(sanitizedCollection)
|
||||
|
||||
let cursor = coll.find(filter)
|
||||
|
||||
if (Object.keys(sortCriteria).length > 0) {
|
||||
cursor = cursor.sort(sortCriteria)
|
||||
}
|
||||
|
||||
const limit = params.limit || 100
|
||||
cursor = cursor.limit(limit)
|
||||
|
||||
const documents = await cursor.toArray()
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Query executed successfully, returned ${documents.length} documents`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Found ${documents.length} documents`,
|
||||
documents,
|
||||
documentCount: documents.length,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] MongoDB query failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `MongoDB query failed: ${errorMessage}` }, { status: 500 })
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
143
apps/sim/app/api/tools/mongodb/update/route.ts
Normal file
143
apps/sim/app/api/tools/mongodb/update/route.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
|
||||
|
||||
const logger = createLogger('MongoDBUpdateAPI')
|
||||
|
||||
const UpdateSchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.coerce.number().int().positive('Port must be a positive integer'),
|
||||
database: z.string().min(1, 'Database name is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
authSource: z.string().optional(),
|
||||
ssl: z.enum(['disabled', 'required', 'preferred']).default('preferred'),
|
||||
collection: z.string().min(1, 'Collection name is required'),
|
||||
filter: z
|
||||
.union([z.string(), z.object({}).passthrough()])
|
||||
.transform((val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val
|
||||
})
|
||||
.refine((val) => val && val.trim() !== '' && val !== '{}', {
|
||||
message: 'Filter is required for MongoDB Update',
|
||||
}),
|
||||
update: z
|
||||
.union([z.string(), z.object({}).passthrough()])
|
||||
.transform((val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
return val
|
||||
})
|
||||
.refine((val) => val && val.trim() !== '', {
|
||||
message: 'Update is required',
|
||||
}),
|
||||
upsert: z
|
||||
.union([z.boolean(), z.string(), z.undefined()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val === 'true' || val === true) return true
|
||||
if (val === 'false' || val === false) return false
|
||||
return false
|
||||
}),
|
||||
multi: z
|
||||
.union([z.boolean(), z.string(), z.undefined()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val === 'true' || val === true) return true
|
||||
if (val === 'false' || val === false) return false
|
||||
return false
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
let client = null
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = UpdateSchema.parse(body)
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Updating document(s) in ${params.host}:${params.port}/${params.database}.${params.collection} (multi: ${params.multi}, upsert: ${params.upsert})`
|
||||
)
|
||||
|
||||
const sanitizedCollection = sanitizeCollectionName(params.collection)
|
||||
|
||||
const filterValidation = validateFilter(params.filter)
|
||||
if (!filterValidation.isValid) {
|
||||
logger.warn(`[${requestId}] Filter validation failed: ${filterValidation.error}`)
|
||||
return NextResponse.json(
|
||||
{ error: `Filter validation failed: ${filterValidation.error}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let filterDoc
|
||||
let updateDoc
|
||||
try {
|
||||
filterDoc = JSON.parse(params.filter)
|
||||
updateDoc = JSON.parse(params.update)
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Invalid JSON in filter or update`)
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid JSON format in filter or update' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
client = await createMongoDBConnection({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl,
|
||||
})
|
||||
|
||||
const db = client.db(params.database)
|
||||
const coll = db.collection(sanitizedCollection)
|
||||
|
||||
let result
|
||||
if (params.multi) {
|
||||
result = await coll.updateMany(filterDoc, updateDoc, { upsert: params.upsert })
|
||||
} else {
|
||||
result = await coll.updateOne(filterDoc, updateDoc, { upsert: params.upsert })
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Update completed: ${result.modifiedCount} modified, ${result.matchedCount} matched${result.upsertedCount ? `, ${result.upsertedCount} upserted` : ''}`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `${result.modifiedCount} documents updated${result.upsertedCount ? `, ${result.upsertedCount} documents upserted` : ''}`,
|
||||
matchedCount: result.matchedCount,
|
||||
modifiedCount: result.modifiedCount,
|
||||
documentCount: result.modifiedCount + (result.upsertedCount || 0),
|
||||
...(result.upsertedId && { insertedId: result.upsertedId.toString() }),
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] MongoDB update failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `MongoDB update failed: ${errorMessage}` }, { status: 500 })
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
123
apps/sim/app/api/tools/mongodb/utils.ts
Normal file
123
apps/sim/app/api/tools/mongodb/utils.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { MongoClient } from 'mongodb'
|
||||
import type { MongoDBConnectionConfig } from '@/tools/mongodb/types'
|
||||
|
||||
export async function createMongoDBConnection(config: MongoDBConnectionConfig) {
|
||||
const credentials =
|
||||
config.username && config.password
|
||||
? `${encodeURIComponent(config.username)}:${encodeURIComponent(config.password)}@`
|
||||
: ''
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (config.authSource) {
|
||||
queryParams.append('authSource', config.authSource)
|
||||
}
|
||||
|
||||
if (config.ssl === 'required') {
|
||||
queryParams.append('ssl', 'true')
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const uri = `mongodb://${credentials}${config.host}:${config.port}/${config.database}${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
const client = new MongoClient(uri, {
|
||||
connectTimeoutMS: 10000,
|
||||
socketTimeoutMS: 10000,
|
||||
maxPoolSize: 1,
|
||||
})
|
||||
|
||||
await client.connect()
|
||||
return client
|
||||
}
|
||||
|
||||
export function validateFilter(filter: string): { isValid: boolean; error?: string } {
|
||||
try {
|
||||
const parsed = JSON.parse(filter)
|
||||
|
||||
const dangerousOperators = ['$where', '$regex', '$expr', '$function', '$accumulator', '$let']
|
||||
|
||||
const checkForDangerousOps = (obj: any): boolean => {
|
||||
if (typeof obj !== 'object' || obj === null) return false
|
||||
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (dangerousOperators.includes(key)) return true
|
||||
if (typeof obj[key] === 'object' && checkForDangerousOps(obj[key])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (checkForDangerousOps(parsed)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Filter contains potentially dangerous operators',
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true }
|
||||
} catch (error) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Invalid JSON format in filter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validatePipeline(pipeline: string): { isValid: boolean; error?: string } {
|
||||
try {
|
||||
const parsed = JSON.parse(pipeline)
|
||||
|
||||
if (!Array.isArray(parsed)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Pipeline must be an array',
|
||||
}
|
||||
}
|
||||
|
||||
const dangerousOperators = [
|
||||
'$where',
|
||||
'$function',
|
||||
'$accumulator',
|
||||
'$let',
|
||||
'$merge',
|
||||
'$out',
|
||||
'$currentOp',
|
||||
'$listSessions',
|
||||
'$listLocalSessions',
|
||||
]
|
||||
|
||||
const checkPipelineStage = (stage: any): boolean => {
|
||||
if (typeof stage !== 'object' || stage === null) return false
|
||||
|
||||
for (const key of Object.keys(stage)) {
|
||||
if (dangerousOperators.includes(key)) return true
|
||||
if (typeof stage[key] === 'object' && checkPipelineStage(stage[key])) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const stage of parsed) {
|
||||
if (checkPipelineStage(stage)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Pipeline contains potentially dangerous operators',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true }
|
||||
} catch (error) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Invalid JSON format in pipeline',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizeCollectionName(name: string): string {
|
||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
||||
throw new Error(
|
||||
'Invalid collection name. Must start with letter or underscore and contain only letters, numbers, and underscores.'
|
||||
)
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import OpenAI, { AzureOpenAI } from 'openai'
|
||||
import { env } from '@/lib/env'
|
||||
import { getCostMultiplier, isBillingEnabled } from '@/lib/environment'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { db } from '@/db'
|
||||
import { userStats, workflow } from '@/db/schema'
|
||||
import { getModelPricing } from '@/providers/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
@@ -47,6 +52,7 @@ interface RequestBody {
|
||||
systemPrompt?: string
|
||||
stream?: boolean
|
||||
history?: ChatMessage[]
|
||||
workflowId?: string
|
||||
}
|
||||
|
||||
function safeStringify(value: unknown): string {
|
||||
@@ -57,6 +63,80 @@ function safeStringify(value: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateUserStatsForWand(
|
||||
workflowId: string,
|
||||
usage: {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
},
|
||||
requestId: string
|
||||
): Promise<void> {
|
||||
if (!isBillingEnabled) {
|
||||
logger.debug(`[${requestId}] Billing is disabled, skipping wand usage cost update`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!usage.total_tokens || usage.total_tokens <= 0) {
|
||||
logger.debug(`[${requestId}] No tokens to update in user stats`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const [workflowRecord] = await db
|
||||
.select({ userId: workflow.userId })
|
||||
.from(workflow)
|
||||
.where(eq(workflow.id, workflowId))
|
||||
.limit(1)
|
||||
|
||||
if (!workflowRecord?.userId) {
|
||||
logger.warn(
|
||||
`[${requestId}] No user found for workflow ${workflowId}, cannot update user stats`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const userId = workflowRecord.userId
|
||||
const totalTokens = usage.total_tokens || 0
|
||||
const promptTokens = usage.prompt_tokens || 0
|
||||
const completionTokens = usage.completion_tokens || 0
|
||||
|
||||
const modelName = useWandAzure ? wandModelName : 'gpt-4o'
|
||||
const pricing = getModelPricing(modelName)
|
||||
|
||||
const costMultiplier = getCostMultiplier()
|
||||
let modelCost = 0
|
||||
|
||||
if (pricing) {
|
||||
const inputCost = (promptTokens / 1000000) * pricing.input
|
||||
const outputCost = (completionTokens / 1000000) * pricing.output
|
||||
modelCost = inputCost + outputCost
|
||||
} else {
|
||||
modelCost = (promptTokens / 1000000) * 0.005 + (completionTokens / 1000000) * 0.015
|
||||
}
|
||||
|
||||
const costToStore = modelCost * costMultiplier
|
||||
|
||||
await db
|
||||
.update(userStats)
|
||||
.set({
|
||||
totalTokensUsed: sql`total_tokens_used + ${totalTokens}`,
|
||||
totalCost: sql`total_cost + ${costToStore}`,
|
||||
currentPeriodCost: sql`current_period_cost + ${costToStore}`,
|
||||
lastActive: new Date(),
|
||||
})
|
||||
.where(eq(userStats.userId, userId))
|
||||
|
||||
logger.debug(`[${requestId}] Updated user stats for wand usage`, {
|
||||
userId,
|
||||
tokensUsed: totalTokens,
|
||||
costAdded: costToStore,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Failed to update user stats for wand usage`, error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
logger.info(`[${requestId}] Received wand generation request`)
|
||||
@@ -72,7 +152,7 @@ export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = (await req.json()) as RequestBody
|
||||
|
||||
const { prompt, systemPrompt, stream = false, history = [] } = body
|
||||
const { prompt, systemPrompt, stream = false, history = [], workflowId } = body
|
||||
|
||||
if (!prompt) {
|
||||
logger.warn(`[${requestId}] Invalid request: Missing prompt.`)
|
||||
@@ -135,7 +215,7 @@ export async function POST(req: NextRequest) {
|
||||
body: JSON.stringify({
|
||||
model: useWandAzure ? wandModelName : 'gpt-4o',
|
||||
messages: messages,
|
||||
temperature: 0.3,
|
||||
temperature: 0.2,
|
||||
max_tokens: 10000,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
@@ -168,6 +248,7 @@ export async function POST(req: NextRequest) {
|
||||
try {
|
||||
let buffer = ''
|
||||
let chunkCount = 0
|
||||
let finalUsage: any = null
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
@@ -213,6 +294,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (parsed.usage) {
|
||||
finalUsage = parsed.usage
|
||||
logger.info(
|
||||
`[${requestId}] Received usage data: ${JSON.stringify(parsed.usage)}`
|
||||
)
|
||||
@@ -231,6 +313,10 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Wand generation streaming completed successfully`)
|
||||
|
||||
if (finalUsage && workflowId) {
|
||||
await updateUserStatsForWand(workflowId, finalUsage, requestId)
|
||||
}
|
||||
} catch (streamError: any) {
|
||||
logger.error(`[${requestId}] Streaming error`, {
|
||||
name: streamError?.name,
|
||||
@@ -297,6 +383,11 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Wand generation successful`)
|
||||
|
||||
if (completion.usage && workflowId) {
|
||||
await updateUserStatsForWand(workflowId, completion.usage, requestId)
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, content: generatedContent })
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Wand generation failed`, {
|
||||
|
||||
@@ -179,20 +179,13 @@ IMPORTANT FORMATTING RULES:
|
||||
return wandConfig
|
||||
}, [wandConfig, remoteExecution, languageValue])
|
||||
|
||||
const wandHook = dynamicWandConfig?.enabled
|
||||
? useWand({
|
||||
wandConfig: dynamicWandConfig,
|
||||
currentValue: code,
|
||||
onStreamStart: () => handleStreamStartRef.current?.(),
|
||||
onStreamChunk: (chunk: string) => {
|
||||
setCode((prev) => prev + chunk)
|
||||
handleStreamChunkRef.current?.(chunk)
|
||||
},
|
||||
onGeneratedContent: (content: string) => {
|
||||
handleGeneratedContentRef.current?.(content)
|
||||
},
|
||||
})
|
||||
: null
|
||||
const wandHook = useWand({
|
||||
wandConfig: wandConfig || { enabled: false, prompt: '' },
|
||||
currentValue: code,
|
||||
onStreamStart: () => handleStreamStartRef.current?.(),
|
||||
onStreamChunk: (chunk: string) => handleStreamChunkRef.current?.(chunk),
|
||||
onGeneratedContent: (content: string) => handleGeneratedContentRef.current?.(content),
|
||||
})
|
||||
|
||||
const isAiLoading = wandHook?.isLoading || false
|
||||
const isAiStreaming = wandHook?.isStreaming || false
|
||||
|
||||
@@ -198,7 +198,6 @@ export function useWand({
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
// Process incoming chunks using SSE format (identical to Chat panel)
|
||||
const chunk = decoder.decode(value)
|
||||
const lines = chunk.split('\n\n')
|
||||
|
||||
@@ -207,26 +206,21 @@ export function useWand({
|
||||
try {
|
||||
const data = JSON.parse(line.substring(6))
|
||||
|
||||
// Check if there's an error
|
||||
if (data.error) {
|
||||
throw new Error(data.error)
|
||||
}
|
||||
|
||||
// Process chunk
|
||||
if (data.chunk) {
|
||||
accumulatedContent += data.chunk
|
||||
// Stream each chunk to the UI immediately
|
||||
if (onStreamChunk) {
|
||||
onStreamChunk(data.chunk)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if streaming is complete
|
||||
if (data.done) {
|
||||
break
|
||||
}
|
||||
} catch (parseError) {
|
||||
// Continue processing other lines
|
||||
logger.debug('Failed to parse SSE line', { line, parseError })
|
||||
}
|
||||
}
|
||||
@@ -239,7 +233,6 @@ export function useWand({
|
||||
if (accumulatedContent) {
|
||||
onGeneratedContent(accumulatedContent)
|
||||
|
||||
// Update conversation history if enabled
|
||||
if (wandConfig.maintainHistory) {
|
||||
setConversationHistory((prev) => [
|
||||
...prev,
|
||||
@@ -248,7 +241,6 @@ export function useWand({
|
||||
])
|
||||
}
|
||||
|
||||
// Call completion callback
|
||||
if (onGenerationComplete) {
|
||||
onGenerationComplete(currentPrompt, accumulatedContent)
|
||||
}
|
||||
|
||||
@@ -556,7 +556,7 @@ const WorkflowContent = React.memo(() => {
|
||||
let autoConnectEdge
|
||||
if (isAutoConnectEnabled && type !== 'starter') {
|
||||
const closestBlock = findClosestOutput(centerPosition)
|
||||
logger.info('🎯 Closest block found:', closestBlock)
|
||||
logger.info('Closest block found:', closestBlock)
|
||||
if (closestBlock) {
|
||||
// Get appropriate source handle
|
||||
const sourceHandle = determineSourceHandle(closestBlock)
|
||||
@@ -569,7 +569,7 @@ const WorkflowContent = React.memo(() => {
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
}
|
||||
logger.info('✅ Auto-connect edge created:', autoConnectEdge)
|
||||
logger.info('Auto-connect edge created:', autoConnectEdge)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
960
apps/sim/blocks/blocks/mongodb.ts
Normal file
960
apps/sim/blocks/blocks/mongodb.ts
Normal file
@@ -0,0 +1,960 @@
|
||||
import { MongoDBIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { MongoDBResponse } from '@/tools/mongodb/types'
|
||||
|
||||
export const MongoDBBlock: BlockConfig<MongoDBResponse> = {
|
||||
type: 'mongodb',
|
||||
name: 'MongoDB',
|
||||
description: 'Connect to MongoDB database',
|
||||
longDescription:
|
||||
'Connect to any MongoDB database to execute queries, manage data, and perform database operations. Supports find, insert, update, delete, and aggregation operations with secure connection handling.',
|
||||
docsLink: 'https://docs.sim.ai/tools/mongodb',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: MongoDBIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Find Documents', id: 'query' },
|
||||
{ label: 'Insert Documents', id: 'insert' },
|
||||
{ label: 'Update Documents', id: 'update' },
|
||||
{ label: 'Delete Documents', id: 'delete' },
|
||||
{ label: 'Aggregate Pipeline', id: 'execute' },
|
||||
],
|
||||
value: () => 'query',
|
||||
},
|
||||
{
|
||||
id: 'host',
|
||||
title: 'Host',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'localhost or your.mongodb.host',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'port',
|
||||
title: 'Port',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: '27017',
|
||||
value: () => '27017',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
title: 'Database Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'your_database',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'mongodb_user',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'password',
|
||||
title: 'Password',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
password: true,
|
||||
placeholder: 'Your database password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'authSource',
|
||||
title: 'Auth Source',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'admin',
|
||||
},
|
||||
{
|
||||
id: 'ssl',
|
||||
title: 'SSL Mode',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Disabled', id: 'disabled' },
|
||||
{ label: 'Required', id: 'required' },
|
||||
{ label: 'Preferred', id: 'preferred' },
|
||||
],
|
||||
value: () => 'preferred',
|
||||
},
|
||||
{
|
||||
id: 'collection',
|
||||
title: 'Collection Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'users',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Query Filter (JSON)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '{"status": "active"}',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that can be used directly in a MongoDB find() operation.
|
||||
|
||||
### FILTER GUIDELINES
|
||||
1. **Syntax**: Use MongoDB query operators and proper JSON syntax
|
||||
2. **Performance**: Consider indexing and query optimization
|
||||
3. **Security**: Use safe query patterns, avoid NoSQL injection risks
|
||||
4. **Data Types**: Use appropriate MongoDB data types (ObjectId, Date, etc.)
|
||||
5. **Efficiency**: Structure filters for optimal query execution
|
||||
|
||||
### MONGODB QUERY OPERATORS
|
||||
|
||||
**Comparison Operators**:
|
||||
- **$eq**: Equals - \`{"status": {"$eq": "active"}}\` or \`{"status": "active"}\`
|
||||
- **$ne**: Not equals - \`{"status": {"$ne": "inactive"}}\`
|
||||
- **$gt**: Greater than - \`{"age": {"$gt": 18}}\`
|
||||
- **$gte**: Greater than or equal - \`{"price": {"$gte": 100}}\`
|
||||
- **$lt**: Less than - \`{"score": {"$lt": 90}}\`
|
||||
- **$lte**: Less than or equal - \`{"rating": {"$lte": 5}}\`
|
||||
- **$in**: In array - \`{"category": {"$in": ["tech", "science"]}}\`
|
||||
- **$nin**: Not in array - \`{"status": {"$nin": ["deleted", "banned"]}}\`
|
||||
|
||||
**Logical Operators**:
|
||||
- **$and**: AND condition - \`{"$and": [{"age": {"$gte": 18}}, {"status": "active"}]}\`
|
||||
- **$or**: OR condition - \`{"$or": [{"status": "active"}, {"status": "pending"}]}\`
|
||||
- **$not**: NOT condition - \`{"age": {"$not": {"$lt": 18}}}\`
|
||||
- **$nor**: NOR condition - \`{"$nor": [{"status": "deleted"}, {"verified": false}]}\`
|
||||
|
||||
**Element Operators**:
|
||||
- **$exists**: Field exists - \`{"email": {"$exists": true}}\`
|
||||
- **$type**: Field type - \`{"count": {"$type": "number"}}\`
|
||||
|
||||
**Array Operators**:
|
||||
- **$all**: All elements match - \`{"tags": {"$all": ["tech", "mongodb"]}}\`
|
||||
- **$elemMatch**: Element matches - \`{"scores": {"$elemMatch": {"$gte": 80, "$lt": 90}}}\`
|
||||
- **$size**: Array size - \`{"items": {"$size": 3}}\`
|
||||
|
||||
**String Operators**:
|
||||
- **$regex**: Regular expression - \`{"name": {"$regex": "^John", "$options": "i"}}\`
|
||||
- **$text**: Text search - \`{"$text": {"$search": "mongodb tutorial"}}\`
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple equality**: "Find active users"
|
||||
→ {"status": "active"}
|
||||
|
||||
**By ObjectId**: "Find document by ID"
|
||||
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
|
||||
|
||||
**Range query**: "Find products between $10 and $100"
|
||||
→ {"price": {"$gte": 10, "$lte": 100}}
|
||||
|
||||
**Multiple conditions**: "Find active premium users"
|
||||
→ {"status": "active", "plan": "premium"}
|
||||
|
||||
**OR condition**: "Find active or pending orders"
|
||||
→ {"$or": [{"status": "active"}, {"status": "pending"}]}
|
||||
|
||||
**Array contains**: "Find users with admin role"
|
||||
→ {"roles": {"$in": ["admin"]}}
|
||||
|
||||
**Text search**: "Find posts containing 'mongodb'"
|
||||
→ {"$text": {"$search": "mongodb"}}
|
||||
|
||||
**Date range**: "Find recent posts (last 30 days)"
|
||||
→ {"createdAt": {"$gte": {"$date": "2024-01-01T00:00:00.000Z"}}}
|
||||
|
||||
**Nested object**: "Find users in New York"
|
||||
→ {"address.city": "New York"}
|
||||
|
||||
**Field exists**: "Find users with email addresses"
|
||||
→ {"email": {"$exists": true, "$ne": null}}
|
||||
|
||||
**Complex AND/OR**: "Find active users who are either premium or have high score"
|
||||
→ {"$and": [{"status": "active"}, {"$or": [{"plan": "premium"}, {"score": {"$gte": 90}}]}]}
|
||||
|
||||
**Array matching**: "Find posts with specific tags"
|
||||
→ {"tags": {"$all": ["javascript", "tutorial"]}}
|
||||
|
||||
**Regex pattern**: "Find users with Gmail addresses"
|
||||
→ {"email": {"$regex": "@gmail\\.com$", "$options": "i"}}
|
||||
|
||||
**Size check**: "Find users with exactly 3 hobbies"
|
||||
→ {"hobbies": {"$size": 3}}
|
||||
|
||||
**Null/undefined check**: "Find incomplete profiles"
|
||||
→ {"$or": [{"profile": null}, {"profile": {"$exists": false}}]}
|
||||
|
||||
### SECURITY CONSIDERATIONS
|
||||
- Avoid directly embedding user input without validation
|
||||
- Use proper data types (ObjectId for IDs, dates for timestamps)
|
||||
- Consider query performance and indexing
|
||||
- Be cautious with regex patterns that could cause performance issues
|
||||
- Validate that field names exist in your schema
|
||||
|
||||
### PERFORMANCE TIPS
|
||||
- Structure filters to use indexed fields first
|
||||
- Use specific equality matches when possible
|
||||
- Avoid complex regex patterns on large collections
|
||||
- Consider using compound indexes for multi-field queries
|
||||
- Use $limit in combination with filters for large result sets
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. The output must be ready to use directly in MongoDB operations.`,
|
||||
placeholder: 'Describe the documents you want to find...',
|
||||
generationType: 'mongodb-filter',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pipeline',
|
||||
title: 'Aggregation Pipeline (JSON Array)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '[{"$group": {"_id": "$status", "count": {"$sum": 1}}}]',
|
||||
condition: { field: 'operation', value: 'execute' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert MongoDB aggregation developer. Create MongoDB aggregation pipelines based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the aggregation pipeline as a valid JSON array. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON array.
|
||||
|
||||
### PIPELINE GUIDELINES
|
||||
1. **Structure**: Always return a JSON array of aggregation stages
|
||||
2. **Performance**: Order stages efficiently - use $match and $limit early when possible
|
||||
3. **Security**: Avoid unsafe operations or overly complex expressions
|
||||
4. **Readability**: Use clear field names and logical stage ordering
|
||||
5. **Data Types**: Handle ObjectId, dates, numbers, and nested objects correctly
|
||||
|
||||
### AGGREGATION STAGES
|
||||
**Filtering & Matching:**
|
||||
- $match: Filter documents (use early in pipeline)
|
||||
- $lookup: Join collections
|
||||
- $facet: Multi-faceted aggregations
|
||||
|
||||
**Grouping & Analysis:**
|
||||
- $group: Group by fields and calculate aggregates
|
||||
- $bucket: Group into predefined ranges
|
||||
- $bucketAuto: Auto-generate buckets
|
||||
- $count: Count documents
|
||||
|
||||
**Transformation:**
|
||||
- $project: Select/transform fields
|
||||
- $addFields: Add computed fields
|
||||
- $set: Set field values
|
||||
- $unset: Remove fields
|
||||
- $unwind: Deconstruct arrays
|
||||
|
||||
**Sorting & Limiting:**
|
||||
- $sort: Sort documents
|
||||
- $limit: Limit results
|
||||
- $skip: Skip documents
|
||||
- $sample: Random sample
|
||||
|
||||
**Advanced:**
|
||||
- $graphLookup: Recursive lookups
|
||||
- $redact: Conditionally reshape documents
|
||||
- $replaceRoot: Replace document root
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Basic Aggregation**: "Count users by status"
|
||||
→ [
|
||||
{"$group": {"_id": "$status", "count": {"$sum": 1}}},
|
||||
{"$sort": {"count": -1}}
|
||||
]
|
||||
|
||||
**Multi-Stage Analysis**: "Get top 10 customers by total order value"
|
||||
→ [
|
||||
{"$match": {"status": "completed"}},
|
||||
{"$group": {
|
||||
"_id": "$customerId",
|
||||
"totalValue": {"$sum": "$amount"},
|
||||
"orderCount": {"$sum": 1}
|
||||
}},
|
||||
{"$sort": {"totalValue": -1}},
|
||||
{"$limit": 10},
|
||||
{"$project": {
|
||||
"customerId": "$_id",
|
||||
"totalValue": 1,
|
||||
"orderCount": 1,
|
||||
"averageOrderValue": {"$divide": ["$totalValue", "$orderCount"]},
|
||||
"_id": 0
|
||||
}}
|
||||
]
|
||||
|
||||
**Complex Join**: "Get users with their recent orders and order totals"
|
||||
→ [
|
||||
{"$lookup": {
|
||||
"from": "orders",
|
||||
"localField": "_id",
|
||||
"foreignField": "userId",
|
||||
"as": "orders"
|
||||
}},
|
||||
{"$unwind": {
|
||||
"path": "$orders",
|
||||
"preserveNullAndEmptyArrays": true
|
||||
}},
|
||||
{"$match": {
|
||||
"$or": [
|
||||
{"orders.createdAt": {"$gte": new Date(Date.now() - 30*24*60*60*1000)}},
|
||||
{"orders": {"$exists": false}}
|
||||
]
|
||||
}},
|
||||
{"$group": {
|
||||
"_id": "$_id",
|
||||
"name": {"$first": "$name"},
|
||||
"email": {"$first": "$email"},
|
||||
"recentOrderCount": {"$sum": {"$cond": [{"$ifNull": ["$orders", false]}, 1, 0]}},
|
||||
"totalOrderValue": {"$sum": {"$ifNull": ["$orders.amount", 0]}}
|
||||
}},
|
||||
{"$sort": {"totalOrderValue": -1}}
|
||||
]
|
||||
|
||||
**Date Analysis**: "Monthly sales trends with growth rates"
|
||||
→ [
|
||||
{"$match": {
|
||||
"createdAt": {"$gte": new Date("2024-01-01")},
|
||||
"status": "completed"
|
||||
}},
|
||||
{"$group": {
|
||||
"_id": {
|
||||
"year": {"$year": "$createdAt"},
|
||||
"month": {"$month": "$createdAt"}
|
||||
},
|
||||
"totalSales": {"$sum": "$amount"},
|
||||
"orderCount": {"$sum": 1}
|
||||
}},
|
||||
{"$sort": {"_id.year": 1, "_id.month": 1}},
|
||||
{"$group": {
|
||||
"_id": null,
|
||||
"monthlyData": {"$push": {
|
||||
"period": "$_id",
|
||||
"totalSales": "$totalSales",
|
||||
"orderCount": "$orderCount"
|
||||
}}
|
||||
}},
|
||||
{"$unwind": {
|
||||
"path": "$monthlyData",
|
||||
"includeArrayIndex": "index"
|
||||
}},
|
||||
{"$project": {
|
||||
"period": "$monthlyData.period",
|
||||
"totalSales": "$monthlyData.totalSales",
|
||||
"orderCount": "$monthlyData.orderCount",
|
||||
"previousSales": {"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]},
|
||||
"growthRate": {"$cond": [
|
||||
{"$and": [
|
||||
{"$gt": ["$index", 0]},
|
||||
{"$gt": [{"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}, 0]}
|
||||
]},
|
||||
{"$multiply": [
|
||||
{"$divide": [
|
||||
{"$subtract": ["$monthlyData.totalSales", {"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}]},
|
||||
{"$arrayElemAt": ["$monthlyData.totalSales", {"$subtract": ["$index", 1]}]}
|
||||
]},
|
||||
100
|
||||
]},
|
||||
null
|
||||
]}
|
||||
}}
|
||||
]
|
||||
|
||||
**Text Search & Filtering**: "Search products with filters and scoring"
|
||||
→ [
|
||||
{"$match": {
|
||||
"$text": {"$search": "laptop gaming"},
|
||||
"category": "electronics",
|
||||
"price": {"$gte": 500, "$lte": 2000}
|
||||
}},
|
||||
{"$addFields": {
|
||||
"searchScore": {"$meta": "textScore"},
|
||||
"priceScore": {"$divide": [{"$subtract": [2000, "$price"]}, 1500]}
|
||||
}},
|
||||
{"$project": {
|
||||
"name": 1,
|
||||
"price": 1,
|
||||
"category": 1,
|
||||
"rating": 1,
|
||||
"reviewCount": 1,
|
||||
"combinedScore": {"$add": [
|
||||
{"$multiply": ["$searchScore", 0.6]},
|
||||
{"$multiply": ["$priceScore", 0.2]},
|
||||
{"$multiply": ["$rating", 0.2]}
|
||||
]}
|
||||
}},
|
||||
{"$sort": {"combinedScore": -1}},
|
||||
{"$limit": 20}
|
||||
]
|
||||
|
||||
**Geo-spatial Analysis**: "Find nearby stores with inventory"
|
||||
→ [
|
||||
{"$geoNear": {
|
||||
"near": {"type": "Point", "coordinates": [-74.005, 40.7128]},
|
||||
"distanceField": "distance",
|
||||
"maxDistance": 10000,
|
||||
"spherical": true
|
||||
}},
|
||||
{"$lookup": {
|
||||
"from": "inventory",
|
||||
"localField": "_id",
|
||||
"foreignField": "storeId",
|
||||
"as": "inventory"
|
||||
}},
|
||||
{"$addFields": {
|
||||
"inventoryCount": {"$size": "$inventory"},
|
||||
"hasProduct": {"$in": ["target-product-id", "$inventory.productId"]}
|
||||
}},
|
||||
{"$match": {"hasProduct": true}},
|
||||
{"$project": {
|
||||
"name": 1,
|
||||
"address": 1,
|
||||
"distance": {"$round": ["$distance", 0]},
|
||||
"inventoryCount": 1
|
||||
}},
|
||||
{"$sort": {"distance": 1}},
|
||||
{"$limit": 5}
|
||||
]
|
||||
|
||||
### PERFORMANCE TIPS
|
||||
- Place $match as early as possible to reduce document flow
|
||||
- Use $limit after $sort for top-N queries
|
||||
- Index fields used in $match, $sort, and $lookup operations
|
||||
- Use $project to reduce document size in multi-stage pipelines
|
||||
- Consider $facet for multiple aggregations on same data
|
||||
|
||||
### SECURITY CONSIDERATIONS
|
||||
- Validate input parameters to prevent injection
|
||||
- Use appropriate read concerns for consistency requirements
|
||||
- Be cautious with $where and $expr in $match stages
|
||||
- Limit pipeline complexity to prevent timeout/resource issues
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the JSON array pipeline - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe the aggregation you want to perform...',
|
||||
generationType: 'mongodb-pipeline',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
{
|
||||
id: 'sort',
|
||||
title: 'Sort (JSON)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '{"createdAt": -1}',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `Write MongoDB sort criteria as JSON.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### EXAMPLES
|
||||
Newest first: {"createdAt": -1}
|
||||
Alphabetical: {"name": 1}
|
||||
Multiple fields: {"category": 1, "price": -1}
|
||||
|
||||
Use 1 for ascending, -1 for descending. Return ONLY valid JSON.`,
|
||||
placeholder: 'Describe how you want to sort the results...',
|
||||
generationType: 'mongodb-sort',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'documents',
|
||||
title: 'Documents (JSON Array)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '[{"name": "John Doe", "email": "john@example.com", "status": "active"}]',
|
||||
condition: { field: 'operation', value: 'insert' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `Write MongoDB documents as JSON array.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### EXAMPLES
|
||||
Simple user: [{"name": "John Doe", "email": "john@example.com", "active": true}]
|
||||
With nested data: [{"user": {"name": "Jane", "profile": {"age": 25, "city": "NYC"}}, "status": "active"}]
|
||||
Multiple docs: [{"name": "User1", "type": "admin"}, {"name": "User2", "type": "user"}]
|
||||
|
||||
Return ONLY valid JSON array - no explanations.`,
|
||||
placeholder: 'Describe the documents you want to insert...',
|
||||
generationType: 'mongodb-documents',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'Filter (JSON)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '{"name": "Alice Test"}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects to target specific documents for UPDATE operations.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that will identify which documents to update.
|
||||
|
||||
### UPDATE FILTER GUIDELINES
|
||||
1. **Precision**: Use specific criteria to target exact documents you want to update
|
||||
2. **Safety**: Avoid broad filters that might update unintended documents
|
||||
3. **Uniqueness**: When possible, use unique identifiers like _id or email
|
||||
4. **Verification**: Consider what happens if multiple documents match your filter
|
||||
|
||||
### MONGODB QUERY OPERATORS
|
||||
|
||||
**Comparison Operators**:
|
||||
- **$eq**: Equals - \`{"status": {"$eq": "active"}}\` or \`{"status": "active"}\`
|
||||
- **$ne**: Not equals - \`{"status": {"$ne": "inactive"}}\`
|
||||
- **$gt**: Greater than - \`{"age": {"$gt": 18}}\`
|
||||
- **$gte**: Greater than or equal - \`{"price": {"$gte": 100}}\`
|
||||
- **$lt**: Less than - \`{"score": {"$lt": 90}}\`
|
||||
- **$lte**: Less than or equal - \`{"rating": {"$lte": 5}}\`
|
||||
- **$in**: In array - \`{"category": {"$in": ["tech", "science"]}}\`
|
||||
- **$nin**: Not in array - \`{"status": {"$nin": ["deleted", "banned"]}}\`
|
||||
|
||||
**Logical Operators**:
|
||||
- **$and**: AND condition - \`{"$and": [{"age": {"$gte": 18}}, {"status": "active"}]}\`
|
||||
- **$or**: OR condition - \`{"$or": [{"status": "active"}, {"status": "pending"}]}\`
|
||||
- **$not**: NOT condition - \`{"age": {"$not": {"$lt": 18}}}\`
|
||||
|
||||
**Element Operators**:
|
||||
- **$exists**: Field exists - \`{"email": {"$exists": true}}\`
|
||||
- **$type**: Field type - \`{"count": {"$type": "number"}}\`
|
||||
|
||||
### UPDATE FILTER EXAMPLES
|
||||
|
||||
**By unique ID**: "Update specific document by ID"
|
||||
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
|
||||
|
||||
**By unique email**: "Update user with specific email"
|
||||
→ {"email": "john.doe@example.com"}
|
||||
|
||||
**By username**: "Update user account by username"
|
||||
→ {"username": "johndoe"}
|
||||
|
||||
**Multiple specific criteria**: "Update active users in sales department"
|
||||
→ {"status": "active", "department": "sales"}
|
||||
|
||||
**Conditional update**: "Update expired premium accounts"
|
||||
→ {"plan": "premium", "expiryDate": {"$lt": {"$date": "2024-01-01T00:00:00.000Z"}}}
|
||||
|
||||
**Status-based update**: "Update all pending orders"
|
||||
→ {"status": "pending"}
|
||||
|
||||
**Range-based update**: "Update products with low stock"
|
||||
→ {"stock": {"$lt": 10}, "active": true}
|
||||
|
||||
**Complex condition**: "Update users who haven't logged in recently"
|
||||
→ {"$and": [{"status": "active"}, {"lastLogin": {"$lt": {"$date": "2023-12-01T00:00:00.000Z"}}}]}
|
||||
|
||||
### SAFETY CONSIDERATIONS FOR UPDATES
|
||||
- **Always test your filter first** with a find() operation to see what documents match
|
||||
- **Use unique identifiers** when updating single documents (_id, email, username)
|
||||
- **Be specific** - avoid filters that might match more documents than intended
|
||||
- **Consider using $and** to combine multiple conditions for precision
|
||||
- **Validate field names** exist in your schema before updating
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. This filter will determine which documents get updated, so be precise and careful.`,
|
||||
placeholder: 'Describe which documents to update...',
|
||||
generationType: 'mongodb-filter',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'update',
|
||||
title: 'Update (JSON)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '{"$set": {"name": "Jane Doe", "email": "jane@example.com"}}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert MongoDB developer. Generate ONLY the raw JSON update operation based on the user's request.
|
||||
The output MUST be a single, valid JSON object representing MongoDB update operators.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### SAFETY CONSIDERATIONS
|
||||
- ALWAYS use atomic operators ($set, $unset, $inc, etc.) - never provide raw field assignments
|
||||
- For destructive operations like $unset or array modifications, ensure the request is intentional
|
||||
- Consider data types when setting values (strings, numbers, booleans, dates, ObjectIds)
|
||||
- Use $addToSet instead of $push to prevent duplicate array elements when appropriate
|
||||
- Validate that increment operations use numeric values
|
||||
|
||||
### MONGODB UPDATE OPERATORS & EXAMPLES
|
||||
|
||||
#### Field Update Operators:
|
||||
- $set (update/create fields): {"$set": {"name": "John Doe", "email": "john@example.com", "lastLogin": new Date()}}
|
||||
- $unset (remove fields): {"$unset": {"temporaryField": "", "deprecatedData": ""}}
|
||||
- $inc (increment numbers): {"$inc": {"views": 1, "score": -5, "balance": 100.50}}
|
||||
- $mul (multiply values): {"$mul": {"price": 0.9, "quantity": 2}}
|
||||
- $min (update if smaller): {"$min": {"lowestScore": 85}}
|
||||
- $max (update if larger): {"$max": {"highestScore": 95}}
|
||||
- $currentDate (set current date): {"$currentDate": {"lastModified": true, "timestamp": {"$type": "date"}}}
|
||||
- $rename (rename fields): {"$rename": {"old_name": "new_name", "temp_field": "permanent_field"}}
|
||||
|
||||
#### Array Update Operators:
|
||||
- $push (add element): {"$push": {"tags": "new-tag", "comments": {"user": "john", "text": "Great post!"}}}
|
||||
- $push with $each (add multiple): {"$push": {"tags": {"$each": ["tag1", "tag2", "tag3"]}}}
|
||||
- $push with $position: {"$push": {"items": {"$each": ["new-item"], "$position": 0}}}
|
||||
- $push with $slice: {"$push": {"recent_activity": {"$each": [{"action": "login"}], "$slice": -10}}}
|
||||
- $addToSet (add unique): {"$addToSet": {"tags": "unique-tag", "categories": {"$each": ["cat1", "cat2"]}}}
|
||||
- $pull (remove matching): {"$pull": {"tags": "unwanted-tag", "items": {"status": "deleted"}}}
|
||||
- $pullAll (remove multiple): {"$pullAll": {"tags": ["tag1", "tag2"], "numbers": [1, 3, 5]}}
|
||||
- $pop (remove first/last): {"$pop": {"queue": -1, "stack": 1}}
|
||||
|
||||
#### Array Element Updates:
|
||||
- Positional $ operator: {"$set": {"comments.$.approved": true, "items.$.quantity": 5}}
|
||||
- Array filters $[]: {"$set": {"items.$[elem].status": "active"}}, arrayFilters: [{"elem.category": "electronics"}]
|
||||
- Multiple positional $[]: {"$inc": {"items.$[item].reviews.$[review].helpful": 1}}
|
||||
|
||||
#### Complex Combinations:
|
||||
- Multiple operations: {"$set": {"name": "Updated Name", "status": "active"}, "$inc": {"version": 1}, "$push": {"history": {"action": "updated", "date": new Date()}}}
|
||||
- Nested field updates: {"$set": {"profile.settings.notifications": true, "profile.lastSeen": new Date()}}
|
||||
- Conditional updates with $cond in aggregation: {"$set": {"discount": {"$cond": [{"$gte": ["$orderAmount", 100]}, 0.1, 0]}}}
|
||||
|
||||
#### Data Type Examples:
|
||||
- String: {"$set": {"name": "John Doe", "status": "active"}}
|
||||
- Number: {"$set": {"age": 25, "score": 87.5}, "$inc": {"points": 100}}
|
||||
- Boolean: {"$set": {"isActive": true, "verified": false}}
|
||||
- Date: {"$set": {"createdAt": new Date("2024-01-01"), "updatedAt": new Date()}}
|
||||
- ObjectId: {"$set": {"userId": ObjectId("507f1f77bcf86cd799439011")}}
|
||||
- Array: {"$set": {"tags": ["mongodb", "database", "nosql"]}}
|
||||
- Object: {"$set": {"metadata": {"source": "api", "version": "1.2", "processed": true}}}
|
||||
|
||||
### IMPORTANT FORMATTING RULES:
|
||||
1. Return ONLY valid JSON - no explanations, comments, or markdown formatting
|
||||
2. Use proper MongoDB update operator syntax
|
||||
3. Ensure all string values are properly quoted
|
||||
4. Use appropriate data types (new Date() for dates, ObjectId() for IDs)
|
||||
5. For array operations, consider whether $addToSet (unique) or $push (allows duplicates) is appropriate
|
||||
6. When incrementing, ensure the value is numeric
|
||||
7. Structure complex nested updates clearly
|
||||
8. Always validate that field paths exist in your data model
|
||||
|
||||
### REFERENCE VARIABLES:
|
||||
You have access to workflow context variables:
|
||||
- Input parameters: Use <paramName> syntax (e.g., <userId>, <newStatus>)
|
||||
- Environment variables: Use {{ENV_VAR}} syntax (e.g., {{DEFAULT_STATUS}})
|
||||
- Previous block outputs: Use <block.previousBlock.output.field> syntax
|
||||
|
||||
Generate the MongoDB update operation that safely and accurately fulfills the user's request.`,
|
||||
placeholder: 'Describe what you want to update...',
|
||||
generationType: 'mongodb-update',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'upsert',
|
||||
title: 'Upsert',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'False', id: 'false' },
|
||||
{ label: 'True', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
},
|
||||
{
|
||||
id: 'multi',
|
||||
title: 'Update Multiple',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'False', id: 'false' },
|
||||
{ label: 'True', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
},
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'Filter (JSON)',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: '{"status": "inactive"}',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert MongoDB developer. Generate MongoDB query filters as JSON objects to target specific documents for DELETION operations.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the MongoDB query filter as valid JSON. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw JSON object that will identify which documents to delete.
|
||||
|
||||
### ⚠️ DELETION SAFETY WARNING ⚠️
|
||||
DELETIONS ARE PERMANENT! This filter will determine which documents are permanently removed from the database. Be extremely careful and specific with your criteria.
|
||||
|
||||
### DELETE FILTER GUIDELINES
|
||||
1. **EXTREME PRECISION**: Use the most specific criteria possible to target exact documents
|
||||
2. **UNIQUE IDENTIFIERS**: Prefer using unique fields like _id, email, or username
|
||||
3. **TEST FIRST**: Always test with find() before deleting to verify what matches
|
||||
4. **BACKUP**: Consider backing up data before bulk deletions
|
||||
5. **VALIDATION**: Double-check that your filter criteria are correct
|
||||
|
||||
### MONGODB QUERY OPERATORS
|
||||
|
||||
**Comparison Operators**:
|
||||
- **$eq**: Equals - \`{"status": {"$eq": "inactive"}}\` or \`{"status": "inactive"}\`
|
||||
- **$ne**: Not equals - \`{"status": {"$ne": "active"}}\`
|
||||
- **$gt**: Greater than - \`{"age": {"$gt": 65}}\`
|
||||
- **$gte**: Greater than or equal - \`{"createdAt": {"$gte": {"$date": "2024-01-01T00:00:00.000Z"}}}\`
|
||||
- **$lt**: Less than - \`{"lastLogin": {"$lt": {"$date": "2023-01-01T00:00:00.000Z"}}}\`
|
||||
- **$lte**: Less than or equal - \`{"score": {"$lte": 0}}\`
|
||||
- **$in**: In array - \`{"status": {"$in": ["deleted", "banned", "inactive"]}}\`
|
||||
- **$nin**: Not in array - \`{"type": {"$nin": ["admin", "moderator"]}}\`
|
||||
|
||||
**Logical Operators**:
|
||||
- **$and**: AND condition - \`{"$and": [{"status": "inactive"}, {"lastLogin": {"$lt": {"$date": "2023-01-01T00:00:00.000Z"}}}]}\`
|
||||
- **$or**: OR condition - \`{"$or": [{"status": "deleted"}, {"status": "banned"}]}\`
|
||||
- **$not**: NOT condition - \`{"status": {"$not": {"$eq": "active"}}}\`
|
||||
|
||||
**Element Operators**:
|
||||
- **$exists**: Field exists - \`{"deletedAt": {"$exists": true}}\`
|
||||
- **$type**: Field type - \`{"tempData": {"$type": "null"}}\`
|
||||
|
||||
### DELETE FILTER EXAMPLES
|
||||
|
||||
**By unique ID**: "Delete specific document by ID"
|
||||
→ {"_id": ObjectId("507f1f77bcf86cd799439011")}
|
||||
|
||||
**By unique email**: "Delete specific user account"
|
||||
→ {"email": "user.to.delete@example.com"}
|
||||
|
||||
**By inactive status**: "Delete inactive user accounts"
|
||||
→ {"status": "inactive"}
|
||||
|
||||
**By deletion flag**: "Delete documents marked for deletion"
|
||||
→ {"markedForDeletion": true}
|
||||
|
||||
**Old temporary data**: "Delete temporary data older than 30 days"
|
||||
→ {"type": "temp", "createdAt": {"$lt": {"$date": "2023-12-01T00:00:00.000Z"}}}
|
||||
|
||||
**Expired sessions**: "Delete expired user sessions"
|
||||
→ {"type": "session", "expiresAt": {"$lt": {"$date": "2024-01-01T00:00:00.000Z"}}}
|
||||
|
||||
**Banned users**: "Delete banned user accounts"
|
||||
→ {"status": "banned", "bannedAt": {"$exists": true}}
|
||||
|
||||
**Multiple criteria**: "Delete inactive accounts with no recent activity"
|
||||
→ {"$and": [{"status": "inactive"}, {"lastLogin": {"$lt": {"$date": "2023-06-01T00:00:00.000Z"}}}, {"verified": false}]}
|
||||
|
||||
**By category**: "Delete draft posts older than 90 days"
|
||||
→ {"status": "draft", "createdAt": {"$lt": {"$date": "2023-10-01T00:00:00.000Z"}}}
|
||||
|
||||
### EXTREME SAFETY CONSIDERATIONS FOR DELETIONS
|
||||
- **⚠️ ALWAYS TEST YOUR FILTER FIRST** with db.collection.find(filter) to see exactly what will be deleted
|
||||
- **Use the most specific criteria possible** - prefer unique identifiers
|
||||
- **Consider soft deletion** (marking as deleted) instead of hard deletion
|
||||
- **Backup important data** before performing bulk deletions
|
||||
- **Avoid broad filters** that might delete more than intended
|
||||
- **Validate field names** exist in your schema
|
||||
- **Document your deletion criteria** for audit purposes
|
||||
- **Consider the impact** on related data and foreign keys
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the MongoDB query filter as valid JSON - no explanations, no markdown, no extra text. This filter will PERMANENTLY DELETE documents, so be extremely careful and precise!`,
|
||||
placeholder: 'Describe which documents to delete...',
|
||||
generationType: 'mongodb-filter',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'multi',
|
||||
title: 'Delete Multiple',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'False', id: 'false' },
|
||||
{ label: 'True', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'mongodb_query',
|
||||
'mongodb_insert',
|
||||
'mongodb_update',
|
||||
'mongodb_delete',
|
||||
'mongodb_execute',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'query':
|
||||
return 'mongodb_query'
|
||||
case 'insert':
|
||||
return 'mongodb_insert'
|
||||
case 'update':
|
||||
return 'mongodb_update'
|
||||
case 'delete':
|
||||
return 'mongodb_delete'
|
||||
case 'execute':
|
||||
return 'mongodb_execute'
|
||||
default:
|
||||
throw new Error(`Invalid MongoDB operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, documents, ...rest } = params
|
||||
|
||||
let parsedDocuments
|
||||
if (documents && typeof documents === 'string' && documents.trim()) {
|
||||
try {
|
||||
parsedDocuments = JSON.parse(documents)
|
||||
} catch (parseError) {
|
||||
const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
|
||||
throw new Error(
|
||||
`Invalid JSON documents format: ${errorMsg}. Please check your JSON syntax.`
|
||||
)
|
||||
}
|
||||
} else if (documents && typeof documents === 'object') {
|
||||
parsedDocuments = documents
|
||||
}
|
||||
|
||||
const connectionConfig = {
|
||||
host: rest.host,
|
||||
port: typeof rest.port === 'string' ? Number.parseInt(rest.port, 10) : rest.port || 27017,
|
||||
database: rest.database,
|
||||
username: rest.username,
|
||||
password: rest.password,
|
||||
authSource: rest.authSource,
|
||||
ssl: rest.ssl || 'preferred',
|
||||
}
|
||||
|
||||
const result: any = { ...connectionConfig }
|
||||
|
||||
if (rest.collection) result.collection = rest.collection
|
||||
if (rest.query) {
|
||||
result.query = typeof rest.query === 'string' ? rest.query : JSON.stringify(rest.query)
|
||||
}
|
||||
if (rest.limit && rest.limit !== '') {
|
||||
result.limit =
|
||||
typeof rest.limit === 'string' ? Number.parseInt(rest.limit, 10) : rest.limit
|
||||
} else {
|
||||
result.limit = 100 // Default to 100 if not provided
|
||||
}
|
||||
if (rest.sort) {
|
||||
result.sort = typeof rest.sort === 'string' ? rest.sort : JSON.stringify(rest.sort)
|
||||
}
|
||||
if (rest.filter) {
|
||||
result.filter =
|
||||
typeof rest.filter === 'string' ? rest.filter : JSON.stringify(rest.filter)
|
||||
}
|
||||
if (rest.update) {
|
||||
result.update =
|
||||
typeof rest.update === 'string' ? rest.update : JSON.stringify(rest.update)
|
||||
}
|
||||
if (rest.pipeline) {
|
||||
result.pipeline =
|
||||
typeof rest.pipeline === 'string' ? rest.pipeline : JSON.stringify(rest.pipeline)
|
||||
}
|
||||
if (rest.upsert) result.upsert = rest.upsert === 'true' || rest.upsert === true
|
||||
if (rest.multi) result.multi = rest.multi === 'true' || rest.multi === true
|
||||
if (parsedDocuments !== undefined) result.documents = parsedDocuments
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Database operation to perform' },
|
||||
host: { type: 'string', description: 'MongoDB host' },
|
||||
port: { type: 'string', description: 'MongoDB port' },
|
||||
database: { type: 'string', description: 'Database name' },
|
||||
username: { type: 'string', description: 'MongoDB username' },
|
||||
password: { type: 'string', description: 'MongoDB password' },
|
||||
authSource: { type: 'string', description: 'Authentication database' },
|
||||
ssl: { type: 'string', description: 'SSL mode' },
|
||||
collection: { type: 'string', description: 'Collection name' },
|
||||
query: { type: 'string', description: 'Query filter as JSON string' },
|
||||
limit: { type: 'number', description: 'Limit number of documents' },
|
||||
sort: { type: 'string', description: 'Sort criteria as JSON string' },
|
||||
documents: { type: 'json', description: 'Documents to insert' },
|
||||
filter: { type: 'string', description: 'Filter criteria as JSON string' },
|
||||
update: { type: 'string', description: 'Update operations as JSON string' },
|
||||
pipeline: { type: 'string', description: 'Aggregation pipeline as JSON string' },
|
||||
upsert: { type: 'boolean', description: 'Create document if not found' },
|
||||
multi: { type: 'boolean', description: 'Operate on multiple documents' },
|
||||
},
|
||||
outputs: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Success or error message describing the operation outcome',
|
||||
},
|
||||
documents: {
|
||||
type: 'array',
|
||||
description: 'Array of documents returned from the operation',
|
||||
},
|
||||
documentCount: {
|
||||
type: 'number',
|
||||
description: 'Number of documents affected by the operation',
|
||||
},
|
||||
insertedId: {
|
||||
type: 'string',
|
||||
description: 'ID of the inserted document (single insert)',
|
||||
},
|
||||
insertedIds: {
|
||||
type: 'array',
|
||||
description: 'Array of IDs for inserted documents (multiple insert)',
|
||||
},
|
||||
modifiedCount: {
|
||||
type: 'number',
|
||||
description: 'Number of documents modified (update operations)',
|
||||
},
|
||||
deletedCount: {
|
||||
type: 'number',
|
||||
description: 'Number of documents deleted (delete operations)',
|
||||
},
|
||||
matchedCount: {
|
||||
type: 'number',
|
||||
description: 'Number of documents matched (update operations)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -131,14 +131,7 @@ export const TranslateBlock: BlockConfig = {
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'openai_chat',
|
||||
'anthropic_chat',
|
||||
'google_chat',
|
||||
'xai_chat',
|
||||
'deepseek_chat',
|
||||
'deepseek_reasoner',
|
||||
],
|
||||
access: ['openai_chat', 'anthropic_chat', 'google_chat'],
|
||||
config: {
|
||||
tool: (params: Record<string, any>) => {
|
||||
const model = params.model || 'gpt-4o'
|
||||
|
||||
@@ -40,6 +40,7 @@ import { MicrosoftExcelBlock } from '@/blocks/blocks/microsoft_excel'
|
||||
import { MicrosoftPlannerBlock } from '@/blocks/blocks/microsoft_planner'
|
||||
import { MicrosoftTeamsBlock } from '@/blocks/blocks/microsoft_teams'
|
||||
import { MistralParseBlock } from '@/blocks/blocks/mistral_parse'
|
||||
import { MongoDBBlock } from '@/blocks/blocks/mongodb'
|
||||
import { MySQLBlock } from '@/blocks/blocks/mysql'
|
||||
import { NotionBlock } from '@/blocks/blocks/notion'
|
||||
import { OneDriveBlock } from '@/blocks/blocks/onedrive'
|
||||
@@ -116,6 +117,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
microsoft_planner: MicrosoftPlannerBlock,
|
||||
microsoft_teams: MicrosoftTeamsBlock,
|
||||
mistral_parse: MistralParseBlock,
|
||||
mongodb: MongoDBBlock,
|
||||
mysql: MySQLBlock,
|
||||
notion: NotionBlock,
|
||||
openai: OpenAIBlock,
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import type { JSX, SVGProps } from 'react'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Basic types
|
||||
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
|
||||
export type ParamType = 'string' | 'number' | 'boolean' | 'json'
|
||||
export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'array' | 'any'
|
||||
|
||||
// Block classification
|
||||
export type BlockCategory = 'blocks' | 'tools' | 'triggers'
|
||||
|
||||
// Valid generation types for AI assistance
|
||||
export type GenerationType =
|
||||
| 'javascript-function-body'
|
||||
| 'typescript-function-body'
|
||||
@@ -19,8 +16,12 @@ export type GenerationType =
|
||||
| 'custom-tool-schema'
|
||||
| 'sql-query'
|
||||
| 'postgrest'
|
||||
| 'mongodb-filter'
|
||||
| 'mongodb-pipeline'
|
||||
| 'mongodb-sort'
|
||||
| 'mongodb-documents'
|
||||
| 'mongodb-update'
|
||||
|
||||
// SubBlock types
|
||||
export type SubBlockType =
|
||||
| 'short-input' // Single line input
|
||||
| 'long-input' // Multi-line input
|
||||
@@ -51,13 +52,10 @@ export type SubBlockType =
|
||||
| 'response-format' // Response structure format
|
||||
| 'file-upload' // File uploader
|
||||
|
||||
// Component width setting
|
||||
export type SubBlockLayout = 'full' | 'half'
|
||||
|
||||
// Tool result extraction
|
||||
export type ExtractToolOutput<T> = T extends ToolResponse ? T['output'] : never
|
||||
|
||||
// Convert tool output to types
|
||||
export type ToolOutputToValueType<T> = T extends Record<string, any>
|
||||
? {
|
||||
[K in keyof T]: T[K] extends string
|
||||
@@ -72,17 +70,14 @@ export type ToolOutputToValueType<T> = T extends Record<string, any>
|
||||
}
|
||||
: never
|
||||
|
||||
// Block output definition
|
||||
export type BlockOutput =
|
||||
| PrimitiveValueType
|
||||
| { [key: string]: PrimitiveValueType | Record<string, any> }
|
||||
|
||||
// Output field definition with optional description
|
||||
export type OutputFieldDefinition =
|
||||
| PrimitiveValueType
|
||||
| { type: PrimitiveValueType; description?: string }
|
||||
|
||||
// Parameter validation rules
|
||||
export interface ParamConfig {
|
||||
type: ParamType
|
||||
description?: string
|
||||
@@ -100,7 +95,6 @@ export interface ParamConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// SubBlock configuration
|
||||
export interface SubBlockConfig {
|
||||
id: string
|
||||
title?: string
|
||||
@@ -177,7 +171,6 @@ export interface SubBlockConfig {
|
||||
dependsOn?: string[]
|
||||
}
|
||||
|
||||
// Main block definition
|
||||
export interface BlockConfig<T extends ToolResponse = ToolResponse> {
|
||||
type: string
|
||||
name: string
|
||||
@@ -209,7 +202,6 @@ export interface BlockConfig<T extends ToolResponse = ToolResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
// Output configuration rules
|
||||
export interface OutputConfig {
|
||||
type: BlockOutput
|
||||
}
|
||||
|
||||
@@ -3452,3 +3452,106 @@ export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function MongoDBIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='currentColor'
|
||||
d='M88.038 42.812c1.605 4.643 2.761 9.383 3.141 14.296.472 6.095.256 12.147-1.029 18.142-.035.165-.109.32-.164.48-.403.001-.814-.049-1.208.012-3.329.523-6.655 1.065-9.981 1.604-3.438.557-6.881 1.092-10.313 1.687-1.216.21-2.721-.041-3.212 1.641-.014.046-.154.054-.235.08l.166-10.051-.169-24.252 1.602-.275c2.62-.429 5.24-.864 7.862-1.281 3.129-.497 6.261-.98 9.392-1.465 1.381-.215 2.764-.412 4.148-.618z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#45A538'
|
||||
d='M61.729 110.054c-1.69-1.453-3.439-2.842-5.059-4.37-8.717-8.222-15.093-17.899-18.233-29.566-.865-3.211-1.442-6.474-1.627-9.792-.13-2.322-.318-4.665-.154-6.975.437-6.144 1.325-12.229 3.127-18.147l.099-.138c.175.233.427.439.516.702 1.759 5.18 3.505 10.364 5.242 15.551 5.458 16.3 10.909 32.604 16.376 48.9.107.318.384.579.583.866l-.87 2.969z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#46A037'
|
||||
d='M88.038 42.812c-1.384.206-2.768.403-4.149.616-3.131.485-6.263.968-9.392 1.465-2.622.417-5.242.852-7.862 1.281l-1.602.275-.012-1.045c-.053-.859-.144-1.717-.154-2.576-.069-5.478-.112-10.956-.18-16.434-.042-3.429-.105-6.857-.175-10.285-.043-2.13-.089-4.261-.185-6.388-.052-1.143-.236-2.28-.311-3.423-.042-.657.016-1.319.029-1.979.817 1.583 1.616 3.178 2.456 4.749 1.327 2.484 3.441 4.314 5.344 6.311 7.523 7.892 12.864 17.068 16.193 27.433z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#409433'
|
||||
d='M65.036 80.753c.081-.026.222-.034.235-.08.491-1.682 1.996-1.431 3.212-1.641 3.432-.594 6.875-1.13 10.313-1.687 3.326-.539 6.652-1.081 9.981-1.604.394-.062.805-.011 1.208-.012-.622 2.22-1.112 4.488-1.901 6.647-.896 2.449-1.98 4.839-3.131 7.182a49.142 49.142 0 01-6.353 9.763c-1.919 2.308-4.058 4.441-6.202 6.548-1.185 1.165-2.582 2.114-3.882 3.161l-.337-.23-1.214-1.038-1.256-2.753a41.402 41.402 0 01-1.394-9.838l.023-.561.171-2.426c.057-.828.133-1.655.168-2.485.129-2.982.241-5.964.359-8.946z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4FAA41'
|
||||
d='M65.036 80.753c-.118 2.982-.23 5.964-.357 8.947-.035.83-.111 1.657-.168 2.485l-.765.289c-1.699-5.002-3.399-9.951-5.062-14.913-2.75-8.209-5.467-16.431-8.213-24.642a4498.887 4498.887 0 00-6.7-19.867c-.105-.31-.407-.552-.617-.826l4.896-9.002c.168.292.39.565.496.879a6167.476 6167.476 0 016.768 20.118c2.916 8.73 5.814 17.467 8.728 26.198.116.349.308.671.491 1.062l.67-.78-.167 10.052z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4AA73C'
|
||||
d='M43.155 32.227c.21.274.511.516.617.826a4498.887 4498.887 0 016.7 19.867c2.746 8.211 5.463 16.433 8.213 24.642 1.662 4.961 3.362 9.911 5.062 14.913l.765-.289-.171 2.426-.155.559c-.266 2.656-.49 5.318-.814 7.968-.163 1.328-.509 2.632-.772 3.947-.198-.287-.476-.548-.583-.866-5.467-16.297-10.918-32.6-16.376-48.9a3888.972 3888.972 0 00-5.242-15.551c-.089-.263-.34-.469-.516-.702l3.272-8.84z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#57AE47'
|
||||
d='M65.202 70.702l-.67.78c-.183-.391-.375-.714-.491-1.062-2.913-8.731-5.812-17.468-8.728-26.198a6167.476 6167.476 0 00-6.768-20.118c-.105-.314-.327-.588-.496-.879l6.055-7.965c.191.255.463.482.562.769 1.681 4.921 3.347 9.848 5.003 14.778 1.547 4.604 3.071 9.215 4.636 13.813.105.308.47.526.714.786l.012 1.045c.058 8.082.115 16.167.171 24.251z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#60B24F'
|
||||
d='M65.021 45.404c-.244-.26-.609-.478-.714-.786-1.565-4.598-3.089-9.209-4.636-13.813-1.656-4.93-3.322-9.856-5.003-14.778-.099-.287-.371-.514-.562-.769 1.969-1.928 3.877-3.925 5.925-5.764 1.821-1.634 3.285-3.386 3.352-5.968.003-.107.059-.214.145-.514l.519 1.306c-.013.661-.072 1.322-.029 1.979.075 1.143.259 2.28.311 3.423.096 2.127.142 4.258.185 6.388.069 3.428.132 6.856.175 10.285.067 5.478.111 10.956.18 16.434.008.861.098 1.718.152 2.577z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#A9AA88'
|
||||
d='M62.598 107.085c.263-1.315.609-2.62.772-3.947.325-2.649.548-5.312.814-7.968l.066-.01.066.011a41.402 41.402 0 001.394 9.838c-.176.232-.425.439-.518.701-.727 2.05-1.412 4.116-2.143 6.166-.1.28-.378.498-.574.744l-.747-2.566.87-2.969z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#B6B598'
|
||||
d='M62.476 112.621c.196-.246.475-.464.574-.744.731-2.05 1.417-4.115 2.143-6.166.093-.262.341-.469.518-.701l1.255 2.754c-.248.352-.59.669-.728 1.061l-2.404 7.059c-.099.283-.437.483-.663.722l-.695-3.985z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#C2C1A7'
|
||||
d='M63.171 116.605c.227-.238.564-.439.663-.722l2.404-7.059c.137-.391.48-.709.728-1.061l1.215 1.037c-.587.58-.913 1.25-.717 2.097l-.369 1.208c-.168.207-.411.387-.494.624-.839 2.403-1.64 4.819-2.485 7.222-.107.305-.404.544-.614.812-.109-1.387-.22-2.771-.331-4.158z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#CECDB7'
|
||||
d='M63.503 120.763c.209-.269.506-.508.614-.812.845-2.402 1.646-4.818 2.485-7.222.083-.236.325-.417.494-.624l-.509 5.545c-.136.157-.333.294-.398.477-.575 1.614-1.117 3.24-1.694 4.854-.119.333-.347.627-.525.938-.158-.207-.441-.407-.454-.623-.051-.841-.016-1.688-.013-2.533z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#DBDAC7'
|
||||
d='M63.969 123.919c.178-.312.406-.606.525-.938.578-1.613 1.119-3.239 1.694-4.854.065-.183.263-.319.398-.477l.012 3.64-1.218 3.124-1.411-.495z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#EBE9DC'
|
||||
d='M65.38 124.415l1.218-3.124.251 3.696-1.469-.572z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#CECDB7'
|
||||
d='M67.464 110.898c-.196-.847.129-1.518.717-2.097l.337.23-1.054 1.867z'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
fill='#4FAA41'
|
||||
d='M64.316 95.172l-.066-.011-.066.01.155-.559-.023.56z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
116
apps/sim/tools/mongodb/delete.ts
Normal file
116
apps/sim/tools/mongodb/delete.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { MongoDBDeleteParams, MongoDBResponse } from '@/tools/mongodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteTool: ToolConfig<MongoDBDeleteParams, MongoDBResponse> = {
|
||||
id: 'mongodb_delete',
|
||||
name: 'MongoDB Delete',
|
||||
description: 'Delete documents from MongoDB collection',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server hostname or IP address',
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server port (default: 27017)',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name to connect to',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB password',
|
||||
},
|
||||
authSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication database',
|
||||
},
|
||||
ssl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'SSL connection mode (disabled, required, preferred)',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Collection name to delete from',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter criteria as JSON string',
|
||||
},
|
||||
multi: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Delete multiple documents',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/mongodb/delete',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl || 'preferred',
|
||||
collection: params.collection,
|
||||
filter: params.filter,
|
||||
multi: params.multi || false,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'MongoDB delete failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Documents deleted successfully',
|
||||
deletedCount: data.deletedCount || 0,
|
||||
documentCount: data.documentCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
deletedCount: { type: 'number', description: 'Number of documents deleted' },
|
||||
documentCount: { type: 'number', description: 'Total number of documents affected' },
|
||||
},
|
||||
}
|
||||
109
apps/sim/tools/mongodb/execute.ts
Normal file
109
apps/sim/tools/mongodb/execute.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { MongoDBExecuteParams, MongoDBResponse } from '@/tools/mongodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const executeTool: ToolConfig<MongoDBExecuteParams, MongoDBResponse> = {
|
||||
id: 'mongodb_execute',
|
||||
name: 'MongoDB Execute',
|
||||
description: 'Execute MongoDB aggregation pipeline',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server hostname or IP address',
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server port (default: 27017)',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name to connect to',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB password',
|
||||
},
|
||||
authSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication database',
|
||||
},
|
||||
ssl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'SSL connection mode (disabled, required, preferred)',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Collection name to execute pipeline on',
|
||||
},
|
||||
pipeline: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Aggregation pipeline as JSON string',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/mongodb/execute',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl || 'preferred',
|
||||
collection: params.collection,
|
||||
pipeline: params.pipeline,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'MongoDB aggregation failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Aggregation executed successfully',
|
||||
documents: data.documents || [],
|
||||
documentCount: data.documentCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
documents: { type: 'array', description: 'Array of documents returned from aggregation' },
|
||||
documentCount: { type: 'number', description: 'Number of documents returned' },
|
||||
},
|
||||
}
|
||||
6
apps/sim/tools/mongodb/index.ts
Normal file
6
apps/sim/tools/mongodb/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { deleteTool } from './delete'
|
||||
export { executeTool } from './execute'
|
||||
export { insertTool } from './insert'
|
||||
export { queryTool } from './query'
|
||||
export * from './types'
|
||||
export { updateTool } from './update'
|
||||
111
apps/sim/tools/mongodb/insert.ts
Normal file
111
apps/sim/tools/mongodb/insert.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { MongoDBInsertParams, MongoDBResponse } from '@/tools/mongodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const insertTool: ToolConfig<MongoDBInsertParams, MongoDBResponse> = {
|
||||
id: 'mongodb_insert',
|
||||
name: 'MongoDB Insert',
|
||||
description: 'Insert documents into MongoDB collection',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server hostname or IP address',
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server port (default: 27017)',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name to connect to',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB password',
|
||||
},
|
||||
authSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication database',
|
||||
},
|
||||
ssl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'SSL connection mode (disabled, required, preferred)',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Collection name to insert into',
|
||||
},
|
||||
documents: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Array of documents to insert',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/mongodb/insert',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl || 'preferred',
|
||||
collection: params.collection,
|
||||
documents: params.documents,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'MongoDB insert failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Documents inserted successfully',
|
||||
documentCount: data.documentCount || 0,
|
||||
insertedId: data.insertedId,
|
||||
insertedIds: data.insertedIds,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
documentCount: { type: 'number', description: 'Number of documents inserted' },
|
||||
insertedId: { type: 'string', description: 'ID of inserted document (single insert)' },
|
||||
insertedIds: { type: 'array', description: 'Array of inserted document IDs (multiple insert)' },
|
||||
},
|
||||
}
|
||||
123
apps/sim/tools/mongodb/query.ts
Normal file
123
apps/sim/tools/mongodb/query.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { MongoDBQueryParams, MongoDBResponse } from '@/tools/mongodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const queryTool: ToolConfig<MongoDBQueryParams, MongoDBResponse> = {
|
||||
id: 'mongodb_query',
|
||||
name: 'MongoDB Query',
|
||||
description: 'Execute find operation on MongoDB collection',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server hostname or IP address',
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server port (default: 27017)',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name to connect to',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB password',
|
||||
},
|
||||
authSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication database',
|
||||
},
|
||||
ssl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'SSL connection mode (disabled, required, preferred)',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Collection name to query',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'MongoDB query filter as JSON string',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of documents to return',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort criteria as JSON string',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/mongodb/query',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl || 'preferred',
|
||||
collection: params.collection,
|
||||
query: params.query,
|
||||
limit: params.limit,
|
||||
sort: params.sort,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'MongoDB query failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Query executed successfully',
|
||||
documents: data.documents || [],
|
||||
documentCount: data.documentCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
documents: { type: 'array', description: 'Array of documents returned from the query' },
|
||||
documentCount: { type: 'number', description: 'Number of documents returned' },
|
||||
},
|
||||
}
|
||||
63
apps/sim/tools/mongodb/types.ts
Normal file
63
apps/sim/tools/mongodb/types.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface MongoDBConnectionConfig {
|
||||
host: string
|
||||
port: number
|
||||
database: string
|
||||
username?: string
|
||||
password?: string
|
||||
authSource?: string
|
||||
ssl?: 'disabled' | 'required' | 'preferred'
|
||||
}
|
||||
|
||||
export interface MongoDBQueryParams extends MongoDBConnectionConfig {
|
||||
collection: string
|
||||
query?: string
|
||||
limit?: number
|
||||
sort?: string
|
||||
}
|
||||
|
||||
export interface MongoDBInsertParams extends MongoDBConnectionConfig {
|
||||
collection: string
|
||||
documents: unknown[]
|
||||
}
|
||||
|
||||
export interface MongoDBUpdateParams extends MongoDBConnectionConfig {
|
||||
collection: string
|
||||
filter: string
|
||||
update: string
|
||||
upsert?: boolean
|
||||
multi?: boolean
|
||||
}
|
||||
|
||||
export interface MongoDBDeleteParams extends MongoDBConnectionConfig {
|
||||
collection: string
|
||||
filter: string
|
||||
multi?: boolean
|
||||
}
|
||||
|
||||
export interface MongoDBExecuteParams extends MongoDBConnectionConfig {
|
||||
collection: string
|
||||
pipeline: string
|
||||
}
|
||||
|
||||
export interface MongoDBBaseResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
documents?: unknown[]
|
||||
documentCount: number
|
||||
insertedId?: string
|
||||
insertedIds?: string[]
|
||||
modifiedCount?: number
|
||||
deletedCount?: number
|
||||
matchedCount?: number
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface MongoDBQueryResponse extends MongoDBBaseResponse {}
|
||||
export interface MongoDBInsertResponse extends MongoDBBaseResponse {}
|
||||
export interface MongoDBUpdateResponse extends MongoDBBaseResponse {}
|
||||
export interface MongoDBDeleteResponse extends MongoDBBaseResponse {}
|
||||
export interface MongoDBExecuteResponse extends MongoDBBaseResponse {}
|
||||
export interface MongoDBResponse extends MongoDBBaseResponse {}
|
||||
134
apps/sim/tools/mongodb/update.ts
Normal file
134
apps/sim/tools/mongodb/update.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { MongoDBResponse, MongoDBUpdateParams } from '@/tools/mongodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateTool: ToolConfig<MongoDBUpdateParams, MongoDBResponse> = {
|
||||
id: 'mongodb_update',
|
||||
name: 'MongoDB Update',
|
||||
description: 'Update documents in MongoDB collection',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server hostname or IP address',
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB server port (default: 27017)',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name to connect to',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'MongoDB password',
|
||||
},
|
||||
authSource: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication database',
|
||||
},
|
||||
ssl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'SSL connection mode (disabled, required, preferred)',
|
||||
},
|
||||
collection: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Collection name to update',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter criteria as JSON string',
|
||||
},
|
||||
update: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Update operations as JSON string',
|
||||
},
|
||||
upsert: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Create document if not found',
|
||||
},
|
||||
multi: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Update multiple documents',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/mongodb/update',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
database: params.database,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
authSource: params.authSource,
|
||||
ssl: params.ssl || 'preferred',
|
||||
collection: params.collection,
|
||||
filter: params.filter,
|
||||
update: params.update,
|
||||
upsert: params.upsert || false,
|
||||
multi: params.multi || false,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'MongoDB update failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Documents updated successfully',
|
||||
matchedCount: data.matchedCount || 0,
|
||||
modifiedCount: data.modifiedCount || 0,
|
||||
documentCount: data.documentCount || 0,
|
||||
insertedId: data.insertedId,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
matchedCount: { type: 'number', description: 'Number of documents matched by filter' },
|
||||
modifiedCount: { type: 'number', description: 'Number of documents modified' },
|
||||
documentCount: { type: 'number', description: 'Total number of documents affected' },
|
||||
insertedId: { type: 'string', description: 'ID of inserted document (if upsert)' },
|
||||
},
|
||||
}
|
||||
@@ -91,6 +91,13 @@ import {
|
||||
microsoftTeamsWriteChatTool,
|
||||
} from '@/tools/microsoft_teams'
|
||||
import { mistralParserTool } from '@/tools/mistral'
|
||||
import {
|
||||
deleteTool as mongodbDeleteTool,
|
||||
executeTool as mongodbExecuteTool,
|
||||
insertTool as mongodbInsertTool,
|
||||
queryTool as mongodbQueryTool,
|
||||
updateTool as mongodbUpdateTool,
|
||||
} from '@/tools/mongodb'
|
||||
import {
|
||||
deleteTool as mysqlDeleteTool,
|
||||
executeTool as mysqlExecuteTool,
|
||||
@@ -242,6 +249,11 @@ export const tools: Record<string, ToolConfig> = {
|
||||
postgresql_update: postgresUpdateTool,
|
||||
postgresql_delete: postgresDeleteTool,
|
||||
postgresql_execute: postgresExecuteTool,
|
||||
mongodb_query: mongodbQueryTool,
|
||||
mongodb_insert: mongodbInsertTool,
|
||||
mongodb_update: mongodbUpdateTool,
|
||||
mongodb_delete: mongodbDeleteTool,
|
||||
mongodb_execute: mongodbExecuteTool,
|
||||
mysql_query: mysqlQueryTool,
|
||||
mysql_insert: mysqlInsertTool,
|
||||
mysql_update: mysqlUpdateTool,
|
||||
|
||||
23
bun.lock
23
bun.lock
@@ -8,6 +8,7 @@
|
||||
"@t3-oss/env-nextjs": "0.13.4",
|
||||
"@vercel/analytics": "1.5.0",
|
||||
"geist": "^1.4.2",
|
||||
"mongodb": "6.19.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"remark-gfm": "4.0.1",
|
||||
"socket.io-client": "4.8.1",
|
||||
@@ -672,6 +673,8 @@
|
||||
|
||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
||||
|
||||
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.3.0", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ=="],
|
||||
|
||||
"@napi-rs/canvas": ["@napi-rs/canvas@0.1.78", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.78", "@napi-rs/canvas-darwin-arm64": "0.1.78", "@napi-rs/canvas-darwin-x64": "0.1.78", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.78", "@napi-rs/canvas-linux-arm64-gnu": "0.1.78", "@napi-rs/canvas-linux-arm64-musl": "0.1.78", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.78", "@napi-rs/canvas-linux-x64-gnu": "0.1.78", "@napi-rs/canvas-linux-x64-musl": "0.1.78", "@napi-rs/canvas-win32-x64-msvc": "0.1.78" } }, "sha512-YaBHJvT+T1DoP16puvWM6w46Lq3VhwKIJ8th5m1iEJyGh7mibk5dT7flBvMQ1EH1LYmMzXJ+OUhu+8wQ9I6u7g=="],
|
||||
|
||||
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.78", "", { "os": "android", "cpu": "arm64" }, "sha512-N1ikxztjrRmh8xxlG5kYm1RuNr8ZW1EINEDQsLhhuy7t0pWI/e7SH91uFVLZKCMDyjel1tyWV93b5fdCAi7ggw=="],
|
||||
@@ -1476,10 +1479,14 @@
|
||||
|
||||
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
|
||||
|
||||
"@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
|
||||
|
||||
"@types/webpack": ["@types/webpack@5.28.5", "", { "dependencies": { "@types/node": "*", "tapable": "^2.2.0", "webpack": "^5" } }, "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw=="],
|
||||
|
||||
"@types/webxr": ["@types/webxr@0.5.23", "", {}, "sha512-GPe4AsfOSpqWd3xA/0gwoKod13ChcfV67trvxaW2krUbgb9gxQjnCx8zGshzMl8LSHZlNH5gQ8LNScsDuc7nGQ=="],
|
||||
|
||||
"@types/whatwg-url": ["@types/whatwg-url@11.0.5", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ=="],
|
||||
|
||||
"@types/xlsx": ["@types/xlsx@0.0.36", "", { "dependencies": { "xlsx": "*" } }, "sha512-mvfrKiKKMErQzLMF8ElYEH21qxWCZtN59pHhWGmWCWFJStYdMWjkDSAy6mGowFxHXaXZWe5/TW7pBUiWclIVOw=="],
|
||||
|
||||
"@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.0", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-sOx1PKSuFwnIl7z4RN0Ls7N9AQawmR9r66eI5rFCzLDIs8HTIYrIpH9QjYWoX0lkgGrkLxXhi4QnK7MizPRrIg=="],
|
||||
@@ -1646,6 +1653,8 @@
|
||||
|
||||
"browserslist": ["browserslist@4.25.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg=="],
|
||||
|
||||
"bson": ["bson@6.10.4", "", {}, "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="],
|
||||
|
||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
||||
@@ -2240,7 +2249,7 @@
|
||||
|
||||
"is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
|
||||
|
||||
"is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
||||
@@ -2430,6 +2439,8 @@
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
@@ -2536,6 +2547,10 @@
|
||||
|
||||
"module-punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"mongodb": ["mongodb@6.19.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", "snappy": "^7.3.2", "socks": "^2.7.1" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw=="],
|
||||
|
||||
"mongodb-connection-string-url": ["mongodb-connection-string-url@3.0.2", "", { "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" } }, "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.23.12", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
|
||||
@@ -2988,6 +3003,8 @@
|
||||
|
||||
"spamc": ["spamc@0.0.5", "", {}, "sha512-jYXItuZuiWZyG9fIdvgTUbp2MNRuyhuSwvvhhpPJd4JK/9oSZxkD7zAj53GJtowSlXwCJzLg6sCKAoE9wXsKgg=="],
|
||||
|
||||
"sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
@@ -3754,6 +3771,8 @@
|
||||
|
||||
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
@@ -3764,8 +3783,6 @@
|
||||
|
||||
"fumadocs-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@t3-oss/env-nextjs": "0.13.4",
|
||||
"@vercel/analytics": "1.5.0",
|
||||
"geist": "^1.4.2",
|
||||
"mongodb": "6.19.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"remark-gfm": "4.0.1",
|
||||
"socket.io-client": "4.8.1"
|
||||
|
||||
Reference in New Issue
Block a user