mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-18 21:21:11 +02:00
added ability to share workflows (#240)
This commit is contained in:
parent
ad7a0d313b
commit
1aaf5929f8
7 changed files with 283 additions and 35 deletions
101
apps/rowboat/app/api/shared-workflow/route.ts
Normal file
101
apps/rowboat/app/api/shared-workflow/route.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Workflow } from '@/app/lib/types/workflow_types';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { db } from '@/app/lib/mongodb';
|
||||||
|
import { SHARED_WORKFLOWS_COLLECTION } from '@/src/infrastructure/repositories/mongodb.shared-workflows.indexes';
|
||||||
|
|
||||||
|
const DEFAULT_TTL_SECONDS = 60 * 60 * 24; // 24 hours
|
||||||
|
|
||||||
|
interface SharedWorkflowDoc {
|
||||||
|
_id: string;
|
||||||
|
workflow: unknown;
|
||||||
|
createdAt: Date;
|
||||||
|
expiresAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateWorkflowJson(obj: unknown) {
|
||||||
|
const parsed = Workflow.safeParse(obj);
|
||||||
|
if (!parsed.success) {
|
||||||
|
const message = parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ');
|
||||||
|
throw new Error(`Invalid workflow JSON: ${message}`);
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const id = searchParams.get('id');
|
||||||
|
const url = searchParams.get('url');
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
const coll = db.collection<SharedWorkflowDoc>(SHARED_WORKFLOWS_COLLECTION);
|
||||||
|
const doc = await coll.findOne(
|
||||||
|
{ _id: id },
|
||||||
|
{ projection: { workflow: 1, expiresAt: 1 } }
|
||||||
|
);
|
||||||
|
if (!doc) {
|
||||||
|
return NextResponse.json({ error: 'Not found or expired' }, { status: 404 });
|
||||||
|
}
|
||||||
|
// Optional safeguard if TTL not yet cleaned up
|
||||||
|
if (doc.expiresAt && doc.expiresAt.getTime() <= Date.now()) {
|
||||||
|
return NextResponse.json({ error: 'Not found or expired' }, { status: 404 });
|
||||||
|
}
|
||||||
|
return NextResponse.json(doc.workflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return NextResponse.json({ error: 'Missing "id" or "url" query param' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('blob:')) {
|
||||||
|
return NextResponse.json({ error: 'Blob URLs are not accessible from the server. Use POST /api/shared-workflow to upload the workflow and share its id.' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHttp = url.startsWith('http://') || url.startsWith('https://');
|
||||||
|
if (!isHttp) {
|
||||||
|
return NextResponse.json({ error: 'Only http(s) URLs are supported in the "url" param' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await fetch(url, { cache: 'no-store' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
return NextResponse.json({ error: `Failed to fetch URL: ${resp.status} ${resp.statusText}` }, { status: 400 });
|
||||||
|
}
|
||||||
|
const text = await resp.text();
|
||||||
|
const obj = JSON.parse(text);
|
||||||
|
const workflow = validateWorkflowJson(obj);
|
||||||
|
return NextResponse.json(workflow);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
return NextResponse.json({ error: message }, { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const contentType = req.headers.get('content-type') || '';
|
||||||
|
let body: any;
|
||||||
|
if (contentType.includes('application/json')) {
|
||||||
|
body = await req.json();
|
||||||
|
} else {
|
||||||
|
const text = await req.text();
|
||||||
|
body = JSON.parse(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowCandidate = typeof body?.workflow === 'object' ? body.workflow : body;
|
||||||
|
const workflow = validateWorkflowJson(workflowCandidate);
|
||||||
|
const id = nanoid();
|
||||||
|
const coll = db.collection<SharedWorkflowDoc>(SHARED_WORKFLOWS_COLLECTION);
|
||||||
|
const now = new Date();
|
||||||
|
const expiresAt = new Date(now.getTime() + DEFAULT_TTL_SECONDS * 1000);
|
||||||
|
await coll.insertOne({ _id: id, workflow, createdAt: now, expiresAt });
|
||||||
|
|
||||||
|
const origin = new URL(req.url).origin;
|
||||||
|
const href = `${origin}/api/shared-workflow?id=${id}`;
|
||||||
|
return NextResponse.json({ id, href, ttlSeconds: DEFAULT_TTL_SECONDS });
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
return NextResponse.json({ error: message }, { status: 400 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Spinner, Tooltip, Input } from "@heroui/react";
|
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Spinner, Tooltip, Input } from "@heroui/react";
|
||||||
import { Button as CustomButton } from "@/components/ui/button";
|
import { Button as CustomButton } from "@/components/ui/button";
|
||||||
import { RadioIcon, RedoIcon, UndoIcon, RocketIcon, PenLine, AlertTriangle, DownloadIcon, SettingsIcon, ChevronDownIcon, ZapIcon, Clock, Plug, MessageCircleIcon } from "lucide-react";
|
import { RadioIcon, RedoIcon, UndoIcon, RocketIcon, PenLine, AlertTriangle, DownloadIcon, SettingsIcon, ChevronDownIcon, ZapIcon, Clock, Plug, MessageCircleIcon, ShareIcon } from "lucide-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { ProgressBar, ProgressStep } from "@/components/ui/progress-bar";
|
import { ProgressBar, ProgressStep } from "@/components/ui/progress-bar";
|
||||||
|
|
||||||
|
|
@ -35,6 +35,9 @@ interface TopBarProps {
|
||||||
onStartTestTour?: () => void;
|
onStartTestTour?: () => void;
|
||||||
onStartPublishTour?: () => void;
|
onStartPublishTour?: () => void;
|
||||||
onStartUseTour?: () => void;
|
onStartUseTour?: () => void;
|
||||||
|
onShareWorkflow: () => void;
|
||||||
|
shareUrl: string | null;
|
||||||
|
onCopyShareUrl: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar({
|
export function TopBar({
|
||||||
|
|
@ -66,6 +69,9 @@ export function TopBar({
|
||||||
onStartTestTour,
|
onStartTestTour,
|
||||||
onStartPublishTour,
|
onStartPublishTour,
|
||||||
onStartUseTour,
|
onStartUseTour,
|
||||||
|
onShareWorkflow,
|
||||||
|
shareUrl,
|
||||||
|
onCopyShareUrl,
|
||||||
}: TopBarProps) {
|
}: TopBarProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
@ -240,6 +246,27 @@ export function TopBar({
|
||||||
<RadioIcon size={16} />
|
<RadioIcon size={16} />
|
||||||
Live workflow
|
Live workflow
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip content="Share Assistant">
|
||||||
|
<button
|
||||||
|
onClick={onShareWorkflow}
|
||||||
|
className="p-1.5 text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300 transition-colors"
|
||||||
|
aria-label="Share Assistant"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<ShareIcon size={20} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{shareUrl && (
|
||||||
|
<Tooltip content="Copy share URL">
|
||||||
|
<button
|
||||||
|
onClick={onCopyShareUrl}
|
||||||
|
className="px-2 py-1 text-xs bg-indigo-50 hover:bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:hover:bg-indigo-900/50 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-700 rounded-md transition-colors"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Copy URL
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<Tooltip content="Download Assistant JSON">
|
<Tooltip content="Download Assistant JSON">
|
||||||
<button
|
<button
|
||||||
onClick={onDownloadJSON}
|
onClick={onDownloadJSON}
|
||||||
|
|
@ -306,6 +333,27 @@ export function TopBar({
|
||||||
<PenLine size={16} />
|
<PenLine size={16} />
|
||||||
Draft workflow
|
Draft workflow
|
||||||
</div>}
|
</div>}
|
||||||
|
<Tooltip content="Share Assistant">
|
||||||
|
<button
|
||||||
|
onClick={onShareWorkflow}
|
||||||
|
className="p-1.5 text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300 transition-colors"
|
||||||
|
aria-label="Share Assistant"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<ShareIcon size={20} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{shareUrl && (
|
||||||
|
<Tooltip content="Copy share URL">
|
||||||
|
<button
|
||||||
|
onClick={onCopyShareUrl}
|
||||||
|
className="px-2 py-1 text-xs bg-indigo-50 hover:bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:hover:bg-indigo-900/50 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-700 rounded-md transition-colors"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Copy URL
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<Tooltip content="Download Assistant JSON">
|
<Tooltip content="Download Assistant JSON">
|
||||||
<button
|
<button
|
||||||
onClick={onDownloadJSON}
|
onClick={onDownloadJSON}
|
||||||
|
|
|
||||||
|
|
@ -1389,15 +1389,12 @@ export function WorkflowEditor({
|
||||||
onRevertModalClose();
|
onRevertModalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove handleCopyJSON and add handleDownloadJSON
|
// Helper: build exported JSON with masked prompt variables
|
||||||
function handleDownloadJSON() {
|
function buildWorkflowExportJson() {
|
||||||
const workflow = state.present.workflow;
|
const workflow = state.present.workflow;
|
||||||
|
|
||||||
// Create a copy of the workflow and replace variable values with dummy text
|
|
||||||
const workflowCopy = {
|
const workflowCopy = {
|
||||||
...workflow,
|
...workflow,
|
||||||
prompts: workflow.prompts.map(prompt => {
|
prompts: workflow.prompts.map(prompt => {
|
||||||
// If this is a variable (base_prompt type), replace its value with dummy text
|
|
||||||
if (prompt.type === 'base_prompt') {
|
if (prompt.type === 'base_prompt') {
|
||||||
return {
|
return {
|
||||||
...prompt,
|
...prompt,
|
||||||
|
|
@ -1407,8 +1404,12 @@ export function WorkflowEditor({
|
||||||
return prompt;
|
return prompt;
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
return JSON.stringify(workflowCopy, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
const json = JSON.stringify(workflowCopy, null, 2);
|
// Download workflow as JSON file
|
||||||
|
function handleDownloadJSON() {
|
||||||
|
const json = buildWorkflowExportJson();
|
||||||
const blob = new Blob([json], { type: 'application/json' });
|
const blob = new Blob([json], { type: 'application/json' });
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
|
|
@ -1420,6 +1421,39 @@ export function WorkflowEditor({
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Share: upload JSON to server to get a share ID and reveal copy button
|
||||||
|
const [shareUrl, setShareUrl] = useState<string | null>(null);
|
||||||
|
async function handleShareWorkflow() {
|
||||||
|
try {
|
||||||
|
// POST to server to create a share token
|
||||||
|
const json = buildWorkflowExportJson();
|
||||||
|
const resp = await fetch('/api/shared-workflow', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: json,
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
console.error('Failed to create share link');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await resp.json();
|
||||||
|
const createUrl = `${window.location.origin}/projects?shared=${encodeURIComponent(data.id)}`;
|
||||||
|
setShareUrl(createUrl);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error sharing workflow:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyShareUrl() {
|
||||||
|
if (!shareUrl) return;
|
||||||
|
navigator.clipboard.writeText(shareUrl);
|
||||||
|
setShowCopySuccess(true);
|
||||||
|
setTimeout(() => setShowCopySuccess(false), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup blob URL on unmount
|
||||||
|
// No-op cleanup; shareUrl is a normal URL now
|
||||||
|
|
||||||
const processQueue = useCallback(async (state: State, dispatch: React.Dispatch<Action>) => {
|
const processQueue = useCallback(async (state: State, dispatch: React.Dispatch<Action>) => {
|
||||||
if (saving.current || saveQueue.current.length === 0) return;
|
if (saving.current || saveQueue.current.length === 0) return;
|
||||||
|
|
||||||
|
|
@ -1689,6 +1723,9 @@ export function WorkflowEditor({
|
||||||
onUndo={() => dispatchGuarded({ type: "undo" })}
|
onUndo={() => dispatchGuarded({ type: "undo" })}
|
||||||
onRedo={() => dispatchGuarded({ type: "redo" })}
|
onRedo={() => dispatchGuarded({ type: "redo" })}
|
||||||
onDownloadJSON={handleDownloadJSON}
|
onDownloadJSON={handleDownloadJSON}
|
||||||
|
onShareWorkflow={handleShareWorkflow}
|
||||||
|
shareUrl={shareUrl}
|
||||||
|
onCopyShareUrl={handleCopyShareUrl}
|
||||||
onPublishWorkflow={handlePublishWorkflow}
|
onPublishWorkflow={handlePublishWorkflow}
|
||||||
onChangeMode={onChangeMode}
|
onChangeMode={onChangeMode}
|
||||||
onRevertToLive={handleRevertToLive}
|
onRevertToLive={handleRevertToLive}
|
||||||
|
|
|
||||||
|
|
@ -156,23 +156,54 @@ export function BuildAssistantSection() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urlPrompt = searchParams.get('prompt');
|
const urlPrompt = searchParams.get('prompt');
|
||||||
const urlTemplate = searchParams.get('template');
|
const urlTemplate = searchParams.get('template');
|
||||||
|
const sharedId = searchParams.get('shared');
|
||||||
|
const importUrl = searchParams.get('importUrl');
|
||||||
|
|
||||||
if (urlPrompt || urlTemplate) {
|
const run = async () => {
|
||||||
setAutoCreateLoading(true);
|
if (sharedId || importUrl) {
|
||||||
createProjectWithOptions({
|
try {
|
||||||
template: urlTemplate || undefined,
|
setAutoCreateLoading(true);
|
||||||
prompt: urlPrompt || undefined,
|
const qs = sharedId ? `id=${encodeURIComponent(sharedId)}` : `url=${encodeURIComponent(importUrl!)}`;
|
||||||
router,
|
const resp = await fetch(`/api/shared-workflow?${qs}`, { cache: 'no-store' });
|
||||||
onError: (error) => {
|
if (!resp.ok) {
|
||||||
console.error('Error auto-creating project:', error);
|
const data = await resp.json().catch(() => ({}));
|
||||||
setAutoCreateLoading(false);
|
throw new Error(data.error || `Failed to load shared workflow (${resp.status})`);
|
||||||
// Fall back to showing the form with the prompt pre-filled
|
|
||||||
if (urlPrompt) {
|
|
||||||
setUserPrompt(urlPrompt);
|
|
||||||
}
|
}
|
||||||
|
const workflowObj = await resp.json();
|
||||||
|
await createProjectFromJsonWithOptions({
|
||||||
|
workflowJson: JSON.stringify(workflowObj),
|
||||||
|
router,
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error creating project from shared workflow:', error);
|
||||||
|
setAutoCreateLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error auto-importing shared workflow:', err);
|
||||||
|
setAutoCreateLoading(false);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
if (urlPrompt || urlTemplate) {
|
||||||
|
setAutoCreateLoading(true);
|
||||||
|
createProjectWithOptions({
|
||||||
|
template: urlTemplate || undefined,
|
||||||
|
prompt: urlPrompt || undefined,
|
||||||
|
router,
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Error auto-creating project:', error);
|
||||||
|
setAutoCreateLoading(false);
|
||||||
|
// Fall back to showing the form with the prompt pre-filled
|
||||||
|
if (urlPrompt) {
|
||||||
|
setUserPrompt(urlPrompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
}, [searchParams, router]);
|
}, [searchParams, router]);
|
||||||
|
|
||||||
const handleCreateAssistant = async () => {
|
const handleCreateAssistant = async () => {
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ export function CreateProject({ defaultName, onOpenProjectPane, isProjectPaneOpe
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const urlPrompt = searchParams.get('prompt');
|
const urlPrompt = searchParams.get('prompt');
|
||||||
const urlTemplate = searchParams.get('template');
|
const urlTemplate = searchParams.get('template');
|
||||||
|
const sharedId = searchParams.get('shared');
|
||||||
|
const importUrl = searchParams.get('importUrl');
|
||||||
|
|
||||||
// Add this effect to update name when defaultName changes
|
// Add this effect to update name when defaultName changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -165,29 +167,48 @@ export function CreateProject({ defaultName, onOpenProjectPane, isProjectPaneOpe
|
||||||
// Add effect to handle URL parameters for auto-creation
|
// Add effect to handle URL parameters for auto-creation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleAutoCreate = async () => {
|
const handleAutoCreate = async () => {
|
||||||
// Only auto-create if we have either a prompt or template, and we're not already loading
|
// Auto-create from template/prompt, or import from shared/id/url
|
||||||
if ((urlPrompt || urlTemplate) && !importLoading && !autoCreateLoading) {
|
if ((urlPrompt || urlTemplate || sharedId || importUrl) && !importLoading && !autoCreateLoading) {
|
||||||
setAutoCreateLoading(true);
|
setAutoCreateLoading(true);
|
||||||
try {
|
try {
|
||||||
await createProjectWithOptions({
|
if (sharedId || importUrl) {
|
||||||
template: urlTemplate || undefined,
|
// Fetch workflow JSON via our API route
|
||||||
prompt: urlPrompt || undefined,
|
const qs = sharedId ? `id=${encodeURIComponent(sharedId)}` : `url=${encodeURIComponent(importUrl!)}`;
|
||||||
router,
|
const resp = await fetch(`/api/shared-workflow?${qs}`, { cache: 'no-store' });
|
||||||
onError: (error) => {
|
if (!resp.ok) {
|
||||||
// Auto-creation failed, show the form instead
|
const data = await resp.json().catch(() => ({}));
|
||||||
setBillingError(error instanceof Error ? error.message : String(error));
|
throw new Error(data.error || `Failed to load shared workflow (${resp.status})`);
|
||||||
setAutoCreateLoading(false);
|
|
||||||
}
|
}
|
||||||
});
|
const workflowObj = await resp.json();
|
||||||
|
await createProjectFromJsonWithOptions({
|
||||||
|
workflowJson: JSON.stringify(workflowObj),
|
||||||
|
router,
|
||||||
|
onError: (error) => {
|
||||||
|
setBillingError(error instanceof Error ? error.message : String(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createProjectWithOptions({
|
||||||
|
template: urlTemplate || undefined,
|
||||||
|
prompt: urlPrompt || undefined,
|
||||||
|
router,
|
||||||
|
onError: (error) => {
|
||||||
|
// Auto-creation failed, show the form instead
|
||||||
|
setBillingError(error instanceof Error ? error.message : String(error));
|
||||||
|
setAutoCreateLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error auto-creating project:', error);
|
console.error('Error auto-creating project:', error);
|
||||||
|
setBillingError(error instanceof Error ? error.message : String(error));
|
||||||
setAutoCreateLoading(false);
|
setAutoCreateLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAutoCreate();
|
handleAutoCreate();
|
||||||
}, [urlPrompt, urlTemplate, importLoading, autoCreateLoading, router]);
|
}, [urlPrompt, urlTemplate, sharedId, importUrl, importLoading, autoCreateLoading, router]);
|
||||||
|
|
||||||
// Inject glow animation styles
|
// Inject glow animation styles
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { RECURRING_JOB_RULES_COLLECTION, RECURRING_JOB_RULES_INDEXES } from "../
|
||||||
import { SCHEDULED_JOB_RULES_COLLECTION, SCHEDULED_JOB_RULES_INDEXES } from "../repositories/mongodb.scheduled-job-rules.indexes";
|
import { SCHEDULED_JOB_RULES_COLLECTION, SCHEDULED_JOB_RULES_INDEXES } from "../repositories/mongodb.scheduled-job-rules.indexes";
|
||||||
import { COMPOSIO_TRIGGER_DEPLOYMENTS_COLLECTION, COMPOSIO_TRIGGER_DEPLOYMENTS_INDEXES } from "../repositories/mongodb.composio-trigger-deployments.indexes";
|
import { COMPOSIO_TRIGGER_DEPLOYMENTS_COLLECTION, COMPOSIO_TRIGGER_DEPLOYMENTS_INDEXES } from "../repositories/mongodb.composio-trigger-deployments.indexes";
|
||||||
import { USERS_COLLECTION, USERS_INDEXES } from "../repositories/mongodb.users.indexes";
|
import { USERS_COLLECTION, USERS_INDEXES } from "../repositories/mongodb.users.indexes";
|
||||||
|
import { SHARED_WORKFLOWS_COLLECTION, SHARED_WORKFLOWS_INDEXES } from "../repositories/mongodb.shared-workflows.indexes";
|
||||||
|
|
||||||
export async function ensureAllIndexes(database: Db): Promise<void> {
|
export async function ensureAllIndexes(database: Db): Promise<void> {
|
||||||
await database.collection(API_KEYS_COLLECTION).createIndexes(API_KEYS_INDEXES);
|
await database.collection(API_KEYS_COLLECTION).createIndexes(API_KEYS_INDEXES);
|
||||||
|
|
@ -23,4 +24,5 @@ export async function ensureAllIndexes(database: Db): Promise<void> {
|
||||||
await database.collection(SCHEDULED_JOB_RULES_COLLECTION).createIndexes(SCHEDULED_JOB_RULES_INDEXES);
|
await database.collection(SCHEDULED_JOB_RULES_COLLECTION).createIndexes(SCHEDULED_JOB_RULES_INDEXES);
|
||||||
await database.collection(COMPOSIO_TRIGGER_DEPLOYMENTS_COLLECTION).createIndexes(COMPOSIO_TRIGGER_DEPLOYMENTS_INDEXES);
|
await database.collection(COMPOSIO_TRIGGER_DEPLOYMENTS_COLLECTION).createIndexes(COMPOSIO_TRIGGER_DEPLOYMENTS_INDEXES);
|
||||||
await database.collection(USERS_COLLECTION).createIndexes(USERS_INDEXES);
|
await database.collection(USERS_COLLECTION).createIndexes(USERS_INDEXES);
|
||||||
|
await database.collection(SHARED_WORKFLOWS_COLLECTION).createIndexes(SHARED_WORKFLOWS_INDEXES);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { IndexDescription } from "mongodb";
|
||||||
|
|
||||||
|
export const SHARED_WORKFLOWS_COLLECTION = "shared_workflows";
|
||||||
|
|
||||||
|
export const SHARED_WORKFLOWS_INDEXES: IndexDescription[] = [
|
||||||
|
{ key: { expiresAt: 1 }, name: "expiresAt_ttl", expireAfterSeconds: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue