diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts index be205cd553..b777ae890f 100644 --- a/apps/docs/app/api/search/route.ts +++ b/apps/docs/app/api/search/route.ts @@ -1,16 +1,126 @@ -import { createFromSource } from 'fumadocs-core/search/server' -import { source } from '@/lib/source' - -export const revalidate = 3600 // Revalidate every hour - -export const { GET } = createFromSource(source, { - localeMap: { - en: { language: 'english' }, - es: { language: 'spanish' }, - fr: { language: 'french' }, - de: { language: 'german' }, - // ja and zh are not supported by the stemmer library, so we'll skip language config for them - ja: {}, - zh: {}, - }, -}) +import { sql } from 'drizzle-orm' +import { type NextRequest, NextResponse } from 'next/server' +import { db, docsEmbeddings } from '@/lib/db' +import { generateSearchEmbedding } from '@/lib/embeddings' + +export const runtime = 'nodejs' +export const revalidate = 0 + +/** + * Hybrid search API endpoint + * - English: Vector embeddings + keyword search + * - Other languages: Keyword search only + */ +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams + const query = searchParams.get('query') || searchParams.get('q') || '' + const locale = searchParams.get('locale') || 'en' + const limit = Number.parseInt(searchParams.get('limit') || '10', 10) + + if (!query || query.trim().length === 0) { + return NextResponse.json([]) + } + + const candidateLimit = limit * 3 + const similarityThreshold = 0.6 + + const localeMap: Record = { + en: 'english', + es: 'spanish', + fr: 'french', + de: 'german', + ja: 'simple', // PostgreSQL doesn't have Japanese support, use simple + zh: 'simple', // PostgreSQL doesn't have Chinese support, use simple + } + const tsConfig = localeMap[locale] || 'simple' + + const useVectorSearch = locale === 'en' + let vectorResults: Array<{ + chunkId: string + chunkText: string + sourceDocument: string + sourceLink: string + headerText: string + headerLevel: number + similarity: number + searchType: string + }> = [] + + if (useVectorSearch) { + const queryEmbedding = await generateSearchEmbedding(query) + vectorResults = await db + .select({ + chunkId: docsEmbeddings.chunkId, + chunkText: docsEmbeddings.chunkText, + sourceDocument: docsEmbeddings.sourceDocument, + sourceLink: docsEmbeddings.sourceLink, + headerText: docsEmbeddings.headerText, + headerLevel: docsEmbeddings.headerLevel, + similarity: sql`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`, + searchType: sql`'vector'`, + }) + .from(docsEmbeddings) + .where( + sql`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector) >= ${similarityThreshold}` + ) + .orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`) + .limit(candidateLimit) + } + + const keywordResults = await db + .select({ + chunkId: docsEmbeddings.chunkId, + chunkText: docsEmbeddings.chunkText, + sourceDocument: docsEmbeddings.sourceDocument, + sourceLink: docsEmbeddings.sourceLink, + headerText: docsEmbeddings.headerText, + headerLevel: docsEmbeddings.headerLevel, + similarity: sql`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query}))`, + searchType: sql`'keyword'`, + }) + .from(docsEmbeddings) + .where(sql`${docsEmbeddings.chunkTextTsv} @@ plainto_tsquery(${tsConfig}, ${query})`) + .orderBy( + sql`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query})) DESC` + ) + .limit(candidateLimit) + + const seenIds = new Set() + const mergedResults = [] + + for (let i = 0; i < Math.max(vectorResults.length, keywordResults.length); i++) { + if (i < vectorResults.length && !seenIds.has(vectorResults[i].chunkId)) { + mergedResults.push(vectorResults[i]) + seenIds.add(vectorResults[i].chunkId) + } + if (i < keywordResults.length && !seenIds.has(keywordResults[i].chunkId)) { + mergedResults.push(keywordResults[i]) + seenIds.add(keywordResults[i].chunkId) + } + } + + const filteredResults = mergedResults.slice(0, limit) + const searchResults = filteredResults.map((result) => { + const title = result.headerText || result.sourceDocument.replace('.mdx', '') + const pathParts = result.sourceDocument + .replace('.mdx', '') + .split('/') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + + return { + id: result.chunkId, + type: 'page' as const, + url: result.sourceLink, + content: title, + breadcrumbs: pathParts, + } + }) + + return NextResponse.json(searchResults) + } catch (error) { + console.error('Semantic search error:', error) + + return NextResponse.json([]) + } +} diff --git a/apps/docs/content/docs/de/execution/costs.mdx b/apps/docs/content/docs/de/execution/costs.mdx index 251e487c82..1f5da14764 100644 --- a/apps/docs/content/docs/de/execution/costs.mdx +++ b/apps/docs/content/docs/de/execution/costs.mdx @@ -105,28 +105,32 @@ Die Modellaufschlüsselung zeigt: Die angezeigten Preise entsprechen den Tarifen vom 10. September 2025. Überprüfen Sie die Dokumentation der Anbieter für aktuelle Preise. +## Bring Your Own Key (BYOK) + +Sie können Ihre eigenen API-Schlüssel für gehostete Modelle (OpenAI, Anthropic, Google, Mistral) unter **Einstellungen → BYOK** verwenden, um Basispreise zu zahlen. Schlüssel werden verschlüsselt und gelten arbeitsbereichsweit. + ## Strategien zur Kostenoptimierung -- **Modellauswahl**: Wählen Sie Modelle basierend auf der Komplexität der Aufgabe. Einfache Aufgaben können GPT-4.1-nano verwenden, während komplexes Denken möglicherweise o1 oder Claude Opus erfordert. -- **Prompt-Engineering**: Gut strukturierte, präzise Prompts reduzieren den Token-Verbrauch ohne Qualitätseinbußen. +- **Modellauswahl**: Wählen Sie Modelle basierend auf der Aufgabenkomplexität. Einfache Aufgaben können GPT-4.1-nano verwenden, während komplexes Reasoning o1 oder Claude Opus erfordern könnte. +- **Prompt Engineering**: Gut strukturierte, prägnante Prompts reduzieren den Token-Verbrauch ohne Qualitätsverlust. - **Lokale Modelle**: Verwenden Sie Ollama oder VLLM für unkritische Aufgaben, um API-Kosten vollständig zu eliminieren. -- **Caching und Wiederverwendung**: Speichern Sie häufig verwendete Ergebnisse in Variablen oder Dateien, um wiederholte KI-Modellaufrufe zu vermeiden. -- **Batch-Verarbeitung**: Verarbeiten Sie mehrere Elemente in einer einzigen KI-Anfrage anstatt einzelne Aufrufe zu tätigen. +- **Caching und Wiederverwendung**: Speichern Sie häufig verwendete Ergebnisse in Variablen oder Dateien, um wiederholte AI-Modellaufrufe zu vermeiden. +- **Batch-Verarbeitung**: Verarbeiten Sie mehrere Elemente in einer einzigen AI-Anfrage, anstatt einzelne Aufrufe zu tätigen. ## Nutzungsüberwachung Überwachen Sie Ihre Nutzung und Abrechnung unter Einstellungen → Abonnement: -- **Aktuelle Nutzung**: Echtzeit-Nutzung und -Kosten für den aktuellen Zeitraum -- **Nutzungslimits**: Plangrenzen mit visuellen Fortschrittsanzeigen +- **Aktuelle Nutzung**: Echtzeit-Nutzung und Kosten für den aktuellen Zeitraum +- **Nutzungslimits**: Plan-Limits mit visuellen Fortschrittsindikatoren - **Abrechnungsdetails**: Prognostizierte Gebühren und Mindestverpflichtungen -- **Planverwaltung**: Upgrade-Optionen und Abrechnungsverlauf +- **Plan-Verwaltung**: Upgrade-Optionen und Abrechnungsverlauf -### Programmatische Nutzungsverfolgung +### Programmatisches Nutzungs-Tracking Sie können Ihre aktuelle Nutzung und Limits programmatisch über die API abfragen: -**Endpunkt:** +**Endpoint:** ```text GET /api/users/me/usage-limits @@ -172,69 +176,69 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt ``` **Rate-Limit-Felder:** -- `requestsPerMinute`: Dauerhafte Rate-Begrenzung (Tokens werden mit dieser Rate aufgefüllt) -- `maxBurst`: Maximale Tokens, die Sie ansammeln können (Burst-Kapazität) -- `remaining`: Aktuell verfügbare Tokens (können bis zu `maxBurst` sein) +- `requestsPerMinute`: Dauerhaftes Rate-Limit (Tokens werden mit dieser Rate aufgefüllt) +- `maxBurst`: Maximale Tokens, die Sie akkumulieren können (Burst-Kapazität) +- `remaining`: Aktuell verfügbare Tokens (kann bis zu `maxBurst` betragen) **Antwortfelder:** -- `currentPeriodCost` spiegelt die Nutzung in der aktuellen Abrechnungsperiode wider -- `limit` wird von individuellen Limits (Free/Pro) oder gepoolten Organisationslimits (Team/Enterprise) abgeleitet -- `plan` ist der aktive Plan mit der höchsten Priorität, der mit Ihrem Benutzer verknüpft ist +- `currentPeriodCost` spiegelt die Nutzung im aktuellen Abrechnungszeitraum wider +- `limit` wird aus individuellen Limits (Free/Pro) oder gepoolten Organisationslimits (Team/Enterprise) abgeleitet +- `plan` ist der Plan mit der höchsten Priorität, der Ihrem Benutzer zugeordnet ist ## Plan-Limits -Verschiedene Abonnementpläne haben unterschiedliche Nutzungslimits: +Verschiedene Abonnement-Pläne haben unterschiedliche Nutzungslimits: | Plan | Monatliches Nutzungslimit | Ratenlimits (pro Minute) | |------|-------------------|-------------------------| -| **Free** | 20 $ | 5 synchron, 10 asynchron | -| **Pro** | 100 $ | 10 synchron, 50 asynchron | -| **Team** | 500 $ (gepoolt) | 50 synchron, 100 asynchron | +| **Free** | 20 $ | 5 sync, 10 async | +| **Pro** | 100 $ | 10 sync, 50 async | +| **Team** | 500 $ (gemeinsam) | 50 sync, 100 async | | **Enterprise** | Individuell | Individuell | ## Abrechnungsmodell -Sim verwendet ein **Basisabonnement + Mehrverbrauch**-Abrechnungsmodell: +Sim verwendet ein **Basis-Abonnement + Mehrverbrauch**-Abrechnungsmodell: -### Wie es funktioniert +### So funktioniert es -**Pro-Plan ($20/Monat):** -- Monatliches Abonnement beinhaltet $20 Nutzung -- Nutzung unter $20 → Keine zusätzlichen Kosten -- Nutzung über $20 → Zahlen Sie den Mehrverbrauch am Monatsende -- Beispiel: $35 Nutzung = $20 (Abonnement) + $15 (Mehrverbrauch) +**Pro-Plan (20 $/Monat):** +- Monatsabonnement beinhaltet 20 $ Nutzung +- Nutzung unter 20 $ → Keine zusätzlichen Gebühren +- Nutzung über 20 $ → Mehrverbrauch am Monatsende zahlen +- Beispiel: 35 $ Nutzung = 20 $ (Abonnement) + 15 $ (Mehrverbrauch) -**Team-Plan ($40/Benutzer/Monat):** -- Gepoolte Nutzung für alle Teammitglieder -- Mehrverbrauch wird aus der Gesamtnutzung des Teams berechnet +**Team-Plan (40 $/Platz/Monat):** +- Gemeinsame Nutzung über alle Teammitglieder +- Mehrverbrauch wird aus der gesamten Team-Nutzung berechnet - Organisationsinhaber erhält eine Rechnung **Enterprise-Pläne:** -- Fester monatlicher Preis, kein Mehrverbrauch +- Fester Monatspreis, kein Mehrverbrauch - Individuelle Nutzungslimits gemäß Vereinbarung ### Schwellenwert-Abrechnung -Wenn der nicht abgerechnete Mehrverbrauch $50 erreicht, berechnet Sim automatisch den gesamten nicht abgerechneten Betrag. +Wenn der nicht abgerechnete Mehrverbrauch 50 $ erreicht, rechnet Sim automatisch den gesamten nicht abgerechneten Betrag ab. **Beispiel:** -- Tag 10: $70 Mehrverbrauch → Sofortige Abrechnung von $70 -- Tag 15: Zusätzliche $35 Nutzung ($105 insgesamt) → Bereits abgerechnet, keine Aktion -- Tag 20: Weitere $50 Nutzung ($155 insgesamt, $85 nicht abgerechnet) → Sofortige Abrechnung von $85 +- Tag 10: 70 $ Mehrverbrauch → 70 $ sofort abrechnen +- Tag 15: Zusätzliche 35 $ Nutzung (105 $ gesamt) → Bereits abgerechnet, keine Aktion +- Tag 20: Weitere 50 $ Nutzung (155 $ gesamt, 85 $ nicht abgerechnet) → 85 $ sofort abrechnen -Dies verteilt große Überziehungsgebühren über den Monat, anstatt eine große Rechnung am Ende des Abrechnungszeitraums zu erhalten. +Dies verteilt große Mehrverbrauchsgebühren über den Monat, anstatt einer großen Rechnung am Periodenende. ## Best Practices für Kostenmanagement 1. **Regelmäßig überwachen**: Überprüfen Sie Ihr Nutzungs-Dashboard häufig, um Überraschungen zu vermeiden -2. **Budgets festlegen**: Nutzen Sie Planlimits als Leitplanken für Ihre Ausgaben +2. **Budgets festlegen**: Nutzen Sie Plan-Limits als Leitplanken für Ihre Ausgaben 3. **Workflows optimieren**: Überprüfen Sie kostenintensive Ausführungen und optimieren Sie Prompts oder Modellauswahl 4. **Passende Modelle verwenden**: Passen Sie die Modellkomplexität an die Aufgabenanforderungen an -5. **Ähnliche Aufgaben bündeln**: Kombinieren Sie wenn möglich mehrere Anfragen, um den Overhead zu reduzieren +5. **Ähnliche Aufgaben bündeln**: Kombinieren Sie mehrere Anfragen, wenn möglich, um Overhead zu reduzieren ## Nächste Schritte - Überprüfen Sie Ihre aktuelle Nutzung unter [Einstellungen → Abonnement](https://sim.ai/settings/subscription) - Erfahren Sie mehr über [Protokollierung](/execution/logging), um Ausführungsdetails zu verfolgen -- Erkunden Sie die [Externe API](/execution/api) für programmatische Kostenüberwachung +- Entdecken Sie die [externe API](/execution/api) für programmatische Kostenüberwachung - Sehen Sie sich [Workflow-Optimierungstechniken](/blocks) an, um Kosten zu reduzieren \ No newline at end of file diff --git a/apps/docs/content/docs/de/triggers/schedule.mdx b/apps/docs/content/docs/de/triggers/schedule.mdx index 4fdb02fc18..d85b1837b7 100644 --- a/apps/docs/content/docs/de/triggers/schedule.mdx +++ b/apps/docs/content/docs/de/triggers/schedule.mdx @@ -56,7 +56,7 @@ Sie müssen Ihren Workflow bereitstellen, damit der Zeitplan mit der Ausführung ## Automatische Deaktivierung -Zeitpläne werden nach **10 aufeinanderfolgenden Fehlschlägen** automatisch deaktiviert, um unkontrollierte Fehler zu verhindern. Bei Deaktivierung: +Zeitpläne werden nach **100 aufeinanderfolgenden Fehlern** automatisch deaktiviert, um unkontrollierte Fehler zu verhindern. Bei Deaktivierung: - Erscheint ein Warnhinweis auf dem Zeitplan-Block - Die Ausführung des Zeitplans wird gestoppt diff --git a/apps/docs/content/docs/en/execution/costs.mdx b/apps/docs/content/docs/en/execution/costs.mdx index 7f1f38adf8..960681b0eb 100644 --- a/apps/docs/content/docs/en/execution/costs.mdx +++ b/apps/docs/content/docs/en/execution/costs.mdx @@ -104,6 +104,10 @@ The model breakdown shows: Pricing shown reflects rates as of September 10, 2025. Check provider documentation for current pricing. +## Bring Your Own Key (BYOK) + +You can use your own API keys for hosted models (OpenAI, Anthropic, Google, Mistral) in **Settings → BYOK** to pay base prices. Keys are encrypted and apply workspace-wide. + ## Cost Optimization Strategies - **Model Selection**: Choose models based on task complexity. Simple tasks can use GPT-4.1-nano while complex reasoning might need o1 or Claude Opus. diff --git a/apps/docs/content/docs/en/triggers/schedule.mdx b/apps/docs/content/docs/en/triggers/schedule.mdx index bb7bfbaa80..ec2f65e91e 100644 --- a/apps/docs/content/docs/en/triggers/schedule.mdx +++ b/apps/docs/content/docs/en/triggers/schedule.mdx @@ -56,7 +56,7 @@ You must deploy your workflow for the schedule to start running. Configure the s ## Automatic Disabling -Schedules automatically disable after **10 consecutive failures** to prevent runaway errors. When disabled: +Schedules automatically disable after **100 consecutive failures** to prevent runaway errors. When disabled: - A warning badge appears on the schedule block - The schedule stops executing diff --git a/apps/docs/content/docs/es/execution/costs.mdx b/apps/docs/content/docs/es/execution/costs.mdx index 1a88efa4b3..52bd7fc577 100644 --- a/apps/docs/content/docs/es/execution/costs.mdx +++ b/apps/docs/content/docs/es/execution/costs.mdx @@ -105,26 +105,30 @@ El desglose del modelo muestra: Los precios mostrados reflejan las tarifas a partir del 10 de septiembre de 2025. Consulta la documentación del proveedor para conocer los precios actuales. +## Trae tu propia clave (BYOK) + +Puedes usar tus propias claves API para modelos alojados (OpenAI, Anthropic, Google, Mistral) en **Configuración → BYOK** para pagar precios base. Las claves están encriptadas y se aplican a todo el espacio de trabajo. + ## Estrategias de optimización de costos -- **Selección de modelos**: Elige modelos según la complejidad de la tarea. Las tareas simples pueden usar GPT-4.1-nano mientras que el razonamiento complejo podría necesitar o1 o Claude Opus. -- **Ingeniería de prompts**: Los prompts bien estructurados y concisos reducen el uso de tokens sin sacrificar la calidad. -- **Modelos locales**: Usa Ollama o VLLM para tareas no críticas para eliminar por completo los costos de API. -- **Almacenamiento en caché y reutilización**: Guarda resultados frecuentemente utilizados en variables o archivos para evitar llamadas repetidas al modelo de IA. -- **Procesamiento por lotes**: Procesa múltiples elementos en una sola solicitud de IA en lugar de hacer llamadas individuales. +- **Selección de modelo**: elige modelos según la complejidad de la tarea. Las tareas simples pueden usar GPT-4.1-nano mientras que el razonamiento complejo podría necesitar o1 o Claude Opus. +- **Ingeniería de prompts**: los prompts bien estructurados y concisos reducen el uso de tokens sin sacrificar calidad. +- **Modelos locales**: usa Ollama o VLLM para tareas no críticas para eliminar completamente los costos de API. +- **Almacenamiento en caché y reutilización**: guarda resultados usados frecuentemente en variables o archivos para evitar llamadas repetidas al modelo de IA. +- **Procesamiento por lotes**: procesa múltiples elementos en una sola solicitud de IA en lugar de hacer llamadas individuales. ## Monitoreo de uso Monitorea tu uso y facturación en Configuración → Suscripción: -- **Uso actual**: Uso y costos en tiempo real para el período actual -- **Límites de uso**: Límites del plan con indicadores visuales de progreso -- **Detalles de facturación**: Cargos proyectados y compromisos mínimos -- **Gestión del plan**: Opciones de actualización e historial de facturación +- **Uso actual**: uso y costos en tiempo real para el período actual +- **Límites de uso**: límites del plan con indicadores visuales de progreso +- **Detalles de facturación**: cargos proyectados y compromisos mínimos +- **Gestión de plan**: opciones de actualización e historial de facturación -### Seguimiento programático de uso +### Seguimiento de uso programático -Puedes consultar tu uso actual y límites de forma programática utilizando la API: +Puedes consultar tu uso y límites actuales de forma programática usando la API: **Endpoint:** @@ -135,13 +139,13 @@ GET /api/users/me/usage-limits **Autenticación:** - Incluye tu clave API en el encabezado `X-API-Key` -**Ejemplo de solicitud:** +**Solicitud de ejemplo:** ```bash curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits ``` -**Ejemplo de respuesta:** +**Respuesta de ejemplo:** ```json { @@ -172,14 +176,14 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt ``` **Campos de límite de tasa:** -- `requestsPerMinute`: Límite de tasa sostenida (los tokens se recargan a esta velocidad) -- `maxBurst`: Máximo de tokens que puedes acumular (capacidad de ráfaga) -- `remaining`: Tokens disponibles actualmente (puede ser hasta `maxBurst`) +- `requestsPerMinute`: límite de tasa sostenida (los tokens se recargan a esta tasa) +- `maxBurst`: tokens máximos que puedes acumular (capacidad de ráfaga) +- `remaining`: tokens actuales disponibles (puede ser hasta `maxBurst`) **Campos de respuesta:** - `currentPeriodCost` refleja el uso en el período de facturación actual -- `limit` se deriva de límites individuales (Gratuito/Pro) o límites agrupados de la organización (Equipo/Empresa) -- `plan` es el plan activo de mayor prioridad asociado a tu usuario +- `limit` se deriva de límites individuales (Free/Pro) o límites de organización agrupados (Team/Enterprise) +- `plan` es el plan activo de mayor prioridad asociado con tu usuario ## Límites del plan @@ -187,10 +191,10 @@ Los diferentes planes de suscripción tienen diferentes límites de uso: | Plan | Límite de uso mensual | Límites de tasa (por minuto) | |------|-------------------|-------------------------| -| **Gratis** | $20 | 5 síncronas, 10 asíncronas | -| **Pro** | $100 | 10 síncronas, 50 asíncronas | -| **Equipo** | $500 (compartido) | 50 síncronas, 100 asíncronas | -| **Empresarial** | Personalizado | Personalizado | +| **Gratuito** | $20 | 5 sync, 10 async | +| **Pro** | $100 | 10 sync, 50 async | +| **Equipo** | $500 (compartido) | 50 sync, 100 async | +| **Empresa** | Personalizado | Personalizado | ## Modelo de facturación @@ -200,16 +204,16 @@ Sim utiliza un modelo de facturación de **suscripción base + excedente**: **Plan Pro ($20/mes):** - La suscripción mensual incluye $20 de uso -- Uso por debajo de $20 → Sin cargos adicionales -- Uso por encima de $20 → Pagas el excedente al final del mes +- Uso inferior a $20 → Sin cargos adicionales +- Uso superior a $20 → Paga el excedente al final del mes - Ejemplo: $35 de uso = $20 (suscripción) + $15 (excedente) -**Plan de Equipo ($40/usuario/mes):** -- Uso agrupado entre todos los miembros del equipo -- Excedente calculado del uso total del equipo +**Plan Equipo ($40/usuario/mes):** +- Uso compartido entre todos los miembros del equipo +- El excedente se calcula a partir del uso total del equipo - El propietario de la organización recibe una sola factura -**Planes Empresariales:** +**Planes Empresa:** - Precio mensual fijo, sin excedentes - Límites de uso personalizados según el acuerdo @@ -218,23 +222,23 @@ Sim utiliza un modelo de facturación de **suscripción base + excedente**: Cuando el excedente no facturado alcanza los $50, Sim factura automáticamente el monto total no facturado. **Ejemplo:** -- Día 10: $70 de excedente → Factura inmediata de $70 -- Día 15: $35 adicionales de uso ($105 en total) → Ya facturado, sin acción -- Día 20: Otros $50 de uso ($155 en total, $85 no facturados) → Factura inmediata de $85 +- Día 10: $70 de excedente → Factura $70 inmediatamente +- Día 15: $35 adicionales de uso ($105 total) → Ya facturado, sin acción +- Día 20: Otros $50 de uso ($155 total, $85 sin facturar) → Factura $85 inmediatamente -Esto distribuye los cargos por exceso a lo largo del mes en lugar de una gran factura al final del período. +Esto distribuye los cargos por excedentes grandes a lo largo del mes en lugar de una sola factura grande al final del período. -## Mejores prácticas para la gestión de costos +## Mejores prácticas de gestión de costos -1. **Monitorear regularmente**: Revisa tu panel de uso con frecuencia para evitar sorpresas -2. **Establecer presupuestos**: Utiliza los límites del plan como guías para tu gasto -3. **Optimizar flujos de trabajo**: Revisa las ejecuciones de alto costo y optimiza los prompts o la selección de modelos -4. **Usar modelos apropiados**: Ajusta la complejidad del modelo a los requisitos de la tarea -5. **Agrupar tareas similares**: Combina múltiples solicitudes cuando sea posible para reducir la sobrecarga +1. **Monitorea regularmente**: Revisa tu panel de uso con frecuencia para evitar sorpresas +2. **Establece presupuestos**: Usa los límites del plan como barreras de protección para tu gasto +3. **Optimiza flujos de trabajo**: Revisa las ejecuciones de alto costo y optimiza los prompts o la selección de modelos +4. **Usa modelos apropiados**: Ajusta la complejidad del modelo a los requisitos de la tarea +5. **Agrupa tareas similares**: Combina múltiples solicitudes cuando sea posible para reducir la sobrecarga ## Próximos pasos - Revisa tu uso actual en [Configuración → Suscripción](https://sim.ai/settings/subscription) -- Aprende sobre [Registro](/execution/logging) para seguir los detalles de ejecución -- Explora la [API externa](/execution/api) para el monitoreo programático de costos -- Consulta las [técnicas de optimización de flujo de trabajo](/blocks) para reducir costos \ No newline at end of file +- Aprende sobre [Registro](/execution/logging) para rastrear detalles de ejecución +- Explora la [API externa](/execution/api) para monitoreo programático de costos +- Consulta las [técnicas de optimización de flujos de trabajo](/blocks) para reducir costos \ No newline at end of file diff --git a/apps/docs/content/docs/es/triggers/schedule.mdx b/apps/docs/content/docs/es/triggers/schedule.mdx index 636e87b19d..7ef0f750d5 100644 --- a/apps/docs/content/docs/es/triggers/schedule.mdx +++ b/apps/docs/content/docs/es/triggers/schedule.mdx @@ -56,7 +56,7 @@ Debes desplegar tu flujo de trabajo para que la programación comience a ejecuta ## Desactivación automática -Las programaciones se desactivan automáticamente después de **10 fallos consecutivos** para evitar errores descontrolados. Cuando se desactiva: +Las programaciones se desactivan automáticamente después de **100 fallos consecutivos** para evitar errores descontrolados. Cuando están desactivadas: - Aparece una insignia de advertencia en el bloque de programación - La programación deja de ejecutarse diff --git a/apps/docs/content/docs/fr/execution/costs.mdx b/apps/docs/content/docs/fr/execution/costs.mdx index 2aa79c32f3..5b34903448 100644 --- a/apps/docs/content/docs/fr/execution/costs.mdx +++ b/apps/docs/content/docs/fr/execution/costs.mdx @@ -105,26 +105,30 @@ La répartition des modèles montre : Les prix indiqués reflètent les tarifs en date du 10 septembre 2025. Consultez la documentation des fournisseurs pour les tarifs actuels. +## Apportez votre propre clé (BYOK) + +Vous pouvez utiliser vos propres clés API pour les modèles hébergés (OpenAI, Anthropic, Google, Mistral) dans **Paramètres → BYOK** pour payer les prix de base. Les clés sont chiffrées et s'appliquent à l'ensemble de l'espace de travail. + ## Stratégies d'optimisation des coûts -- **Sélection du modèle** : choisissez les modèles en fonction de la complexité de la tâche. Les tâches simples peuvent utiliser GPT-4.1-nano tandis que le raisonnement complexe pourrait nécessiter o1 ou Claude Opus. -- **Ingénierie de prompt** : des prompts bien structurés et concis réduisent l'utilisation de tokens sans sacrifier la qualité. +- **Sélection du modèle** : choisissez les modèles en fonction de la complexité de la tâche. Les tâches simples peuvent utiliser GPT-4.1-nano tandis que le raisonnement complexe peut nécessiter o1 ou Claude Opus. +- **Ingénierie des prompts** : des prompts bien structurés et concis réduisent l'utilisation de jetons sans sacrifier la qualité. - **Modèles locaux** : utilisez Ollama ou VLLM pour les tâches non critiques afin d'éliminer complètement les coûts d'API. -- **Mise en cache et réutilisation** : stockez les résultats fréquemment utilisés dans des variables ou des fichiers pour éviter des appels répétés aux modèles d'IA. -- **Traitement par lots** : traitez plusieurs éléments dans une seule requête d'IA plutôt que de faire des appels individuels. +- **Mise en cache et réutilisation** : stockez les résultats fréquemment utilisés dans des variables ou des fichiers pour éviter les appels répétés aux modèles d'IA. +- **Traitement par lots** : traitez plusieurs éléments dans une seule requête d'IA plutôt que d'effectuer des appels individuels. -## Suivi de l'utilisation +## Surveillance de l'utilisation Surveillez votre utilisation et votre facturation dans Paramètres → Abonnement : - **Utilisation actuelle** : utilisation et coûts en temps réel pour la période en cours -- **Limites d'utilisation** : limites du forfait avec indicateurs visuels de progression -- **Détails de facturation** : frais prévisionnels et engagements minimums +- **Limites d'utilisation** : limites du forfait avec indicateurs de progression visuels +- **Détails de facturation** : frais projetés et engagements minimums - **Gestion du forfait** : options de mise à niveau et historique de facturation -### Suivi d'utilisation programmatique +### Suivi programmatique de l'utilisation -Vous pouvez interroger votre utilisation actuelle et vos limites par programmation en utilisant l'API : +Vous pouvez interroger votre utilisation et vos limites actuelles de manière programmatique à l'aide de l'API : **Point de terminaison :** @@ -172,14 +176,14 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt ``` **Champs de limite de débit :** -- `requestsPerMinute` : limite de débit soutenu (les jetons se rechargent à ce rythme) +- `requestsPerMinute` : limite de débit soutenue (les jetons se rechargent à ce rythme) - `maxBurst` : nombre maximum de jetons que vous pouvez accumuler (capacité de rafale) - `remaining` : jetons actuellement disponibles (peut aller jusqu'à `maxBurst`) **Champs de réponse :** - `currentPeriodCost` reflète l'utilisation dans la période de facturation actuelle -- `limit` est dérivé des limites individuelles (Gratuit/Pro) ou des limites mutualisées de l'organisation (Équipe/Entreprise) -- `plan` est le plan actif de plus haute priorité associé à votre utilisateur +- `limit` est dérivé des limites individuelles (Free/Pro) ou des limites d'organisation mutualisées (Team/Enterprise) +- `plan` est le forfait actif de priorité la plus élevée associé à votre utilisateur ## Limites des forfaits @@ -196,21 +200,21 @@ Les différents forfaits d'abonnement ont des limites d'utilisation différentes Sim utilise un modèle de facturation **abonnement de base + dépassement** : -### Comment ça fonctionne +### Fonctionnement **Forfait Pro (20 $/mois) :** - L'abonnement mensuel inclut 20 $ d'utilisation -- Utilisation inférieure à 20 $ → Pas de frais supplémentaires +- Utilisation inférieure à 20 $ → Aucun frais supplémentaire - Utilisation supérieure à 20 $ → Paiement du dépassement en fin de mois - Exemple : 35 $ d'utilisation = 20 $ (abonnement) + 15 $ (dépassement) -**Forfait Équipe (40 $/siège/mois) :** -- Utilisation mutualisée pour tous les membres de l'équipe -- Dépassement calculé à partir de l'utilisation totale de l'équipe +**Forfait Équipe (40 $/utilisateur/mois) :** +- Utilisation mutualisée entre tous les membres de l'équipe +- Dépassement calculé sur l'utilisation totale de l'équipe - Le propriétaire de l'organisation reçoit une seule facture **Forfaits Entreprise :** -- Prix mensuel fixe, pas de dépassements +- Prix mensuel fixe, sans dépassement - Limites d'utilisation personnalisées selon l'accord ### Facturation par seuil @@ -220,21 +224,21 @@ Lorsque le dépassement non facturé atteint 50 $, Sim facture automatiquement l **Exemple :** - Jour 10 : 70 $ de dépassement → Facturation immédiate de 70 $ - Jour 15 : 35 $ d'utilisation supplémentaire (105 $ au total) → Déjà facturé, aucune action -- Jour 20 : 50 $ d'utilisation supplémentaire (155 $ au total, 85 $ non facturés) → Facturation immédiate de 85 $ +- Jour 20 : 50 $ d'utilisation supplémentaire (155 $ au total, 85 $ non facturé) → Facturation immédiate de 85 $ Cela répartit les frais de dépassement importants tout au long du mois au lieu d'une seule facture importante en fin de période. -## Meilleures pratiques de gestion des coûts +## Bonnes pratiques de gestion des coûts -1. **Surveillez régulièrement** : vérifiez fréquemment votre tableau de bord d'utilisation pour éviter les surprises -2. **Définissez des budgets** : utilisez les limites du plan comme garde-fous pour vos dépenses -3. **Optimisez les flux de travail** : examinez les exécutions à coût élevé et optimisez les prompts ou la sélection de modèles -4. **Utilisez des modèles appropriés** : adaptez la complexité du modèle aux exigences de la tâche -5. **Regroupez les tâches similaires** : combinez plusieurs requêtes lorsque c'est possible pour réduire les frais généraux +1. **Surveillez régulièrement** : Consultez fréquemment votre tableau de bord d'utilisation pour éviter les surprises +2. **Définissez des budgets** : Utilisez les limites des forfaits comme garde-fous pour vos dépenses +3. **Optimisez les flux de travail** : Examinez les exécutions coûteuses et optimisez les prompts ou la sélection de modèles +4. **Utilisez les modèles appropriés** : Adaptez la complexité du modèle aux exigences de la tâche +5. **Regroupez les tâches similaires** : Combinez plusieurs requêtes lorsque c'est possible pour réduire les frais généraux ## Prochaines étapes -- Examinez votre utilisation actuelle dans [Paramètres → Abonnement](https://sim.ai/settings/subscription) -- Apprenez-en plus sur la [Journalisation](/execution/logging) pour suivre les détails d'exécution +- Consultez votre utilisation actuelle dans [Paramètres → Abonnement](https://sim.ai/settings/subscription) +- Découvrez la [journalisation](/execution/logging) pour suivre les détails d'exécution - Explorez l'[API externe](/execution/api) pour la surveillance programmatique des coûts -- Consultez les [techniques d'optimisation de flux de travail](/blocks) pour réduire les coûts \ No newline at end of file +- Consultez les [techniques d'optimisation des workflows](/blocks) pour réduire les coûts \ No newline at end of file diff --git a/apps/docs/content/docs/fr/triggers/schedule.mdx b/apps/docs/content/docs/fr/triggers/schedule.mdx index df9e112687..746b92a88f 100644 --- a/apps/docs/content/docs/fr/triggers/schedule.mdx +++ b/apps/docs/content/docs/fr/triggers/schedule.mdx @@ -56,7 +56,7 @@ Vous devez déployer votre workflow pour que la planification commence à s'exé ## Désactivation automatique -Les planifications se désactivent automatiquement après **10 échecs consécutifs** pour éviter les erreurs incontrôlées. Lorsqu'elle est désactivée : +Les planifications se désactivent automatiquement après **100 échecs consécutifs** pour éviter les erreurs en cascade. Lorsqu'elles sont désactivées : - Un badge d'avertissement apparaît sur le bloc de planification - La planification cesse de s'exécuter diff --git a/apps/docs/content/docs/ja/execution/costs.mdx b/apps/docs/content/docs/ja/execution/costs.mdx index 1c587e3d1f..efbbedaaf4 100644 --- a/apps/docs/content/docs/ja/execution/costs.mdx +++ b/apps/docs/content/docs/ja/execution/costs.mdx @@ -105,43 +105,47 @@ AIブロックを使用するワークフローでは、ログで詳細なコス 表示価格は2025年9月10日時点のレートを反映しています。最新の価格については各プロバイダーのドキュメントをご確認ください。 +## Bring Your Own Key (BYOK) + +ホストされたモデル(OpenAI、Anthropic、Google、Mistral)に対して、**設定 → BYOK**で独自のAPIキーを使用し、基本価格で支払うことができます。キーは暗号化され、ワークスペース全体に適用されます。 + ## コスト最適化戦略 -- **モデル選択**: タスクの複雑さに基づいてモデルを選択してください。単純なタスクにはGPT-4.1-nanoを使用し、複雑な推論にはo1やClaude Opusが必要な場合があります。 -- **プロンプトエンジニアリング**: 構造化された簡潔なプロンプトは、品質を犠牲にすることなくトークン使用量を削減します。 -- **ローカルモデル**: 重要度の低いタスクにはOllamaやVLLMを使用して、API費用を完全に排除します。 -- **キャッシュと再利用**: 頻繁に使用される結果を変数やファイルに保存して、AIモデル呼び出しの繰り返しを避けます。 +- **モデルの選択**: タスクの複雑さに基づいてモデルを選択します。シンプルなタスクにはGPT-4.1-nanoを使用し、複雑な推論にはo1やClaude Opusが必要になる場合があります。 +- **プロンプトエンジニアリング**: 適切に構造化された簡潔なプロンプトは、品質を犠牲にすることなくトークン使用量を削減します。 +- **ローカルモデル**: 重要度の低いタスクにはOllamaやVLLMを使用して、APIコストを完全に排除します。 +- **キャッシュと再利用**: 頻繁に使用される結果を変数やファイルに保存して、AIモデルの繰り返し呼び出しを回避します。 - **バッチ処理**: 個別の呼び出しを行うのではなく、単一のAIリクエストで複数のアイテムを処理します。 -## 使用状況モニタリング +## 使用状況の監視 -設定 → サブスクリプションで使用状況と請求を監視できます: +設定 → サブスクリプションで使用状況と請求を監視します: - **現在の使用状況**: 現在の期間のリアルタイムの使用状況とコスト -- **使用制限**: 視覚的な進捗指標付きのプラン制限 -- **請求詳細**: 予測される料金と最低利用額 +- **使用制限**: 視覚的な進行状況インジケーター付きのプラン制限 +- **請求詳細**: 予測される料金と最低コミットメント - **プラン管理**: アップグレードオプションと請求履歴 ### プログラムによる使用状況の追跡 -APIを使用して、現在の使用状況と制限をプログラムで照会できます: +APIを使用して、現在の使用状況と制限をプログラムでクエリできます: -**エンドポイント:** +**エンドポイント:** ```text GET /api/users/me/usage-limits ``` -**認証:** -- APIキーを `X-API-Key` ヘッダーに含めてください +**認証:** +- `X-API-Key`ヘッダーにAPIキーを含めます -**リクエスト例:** +**リクエスト例:** ```bash curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits ``` -**レスポンス例:** +**レスポンス例:** ```json { @@ -171,70 +175,70 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt } ``` -**レート制限フィールド:** -- `requestsPerMinute`:持続的なレート制限(トークンはこの速度で補充されます) -- `maxBurst`:蓄積できる最大トークン数(バースト容量) -- `remaining`:現在利用可能なトークン(最大で`maxBurst`まで) +**レート制限フィールド:** +- `requestsPerMinute`: 持続的なレート制限(トークンはこのレートで補充されます) +- `maxBurst`: 蓄積できる最大トークン数(バースト容量) +- `remaining`: 現在利用可能なトークン数(最大`maxBurst`まで) -**レスポンスフィールド:** +**レスポンスフィールド:** - `currentPeriodCost`は現在の請求期間の使用状況を反映します -- `limit`は個別の制限(無料/プロ)または組織のプール制限(チーム/エンタープライズ)から派生します -- `plan`はユーザーに関連付けられた最優先のアクティブなプランです +- `limit`は個別の制限(Free/Pro)またはプールされた組織の制限(Team/Enterprise)から導出されます +- `plan`はユーザーに関連付けられた最も優先度の高いアクティブなプランです -## プラン制限 +## プランの制限 -サブスクリプションプランによって使用制限が異なります: +サブスクリプションプランによって、使用量の制限が異なります。 -| プラン | 月間使用制限 | レート制限(毎分) | +| プラン | 月間使用量制限 | レート制限(1分あたり) | |------|-------------------|-------------------------| -| **Free** | $20 | 同期5、非同期10 | -| **Pro** | $100 | 同期10、非同期50 | -| **Team** | $500(プール) | 同期50、非同期100 | -| **Enterprise** | カスタム | カスタム | +| **無料** | $20 | 同期5、非同期10 | +| **プロ** | $100 | 同期10、非同期50 | +| **チーム** | $500(プール) | 同期50、非同期100 | +| **エンタープライズ** | カスタム | カスタム | ## 課金モデル -Simは**基本サブスクリプション+超過分**の課金モデルを使用しています: +Simは**基本サブスクリプション + 超過料金**の課金モデルを採用しています。 ### 仕組み **プロプラン(月額$20):** - 月額サブスクリプションには$20分の使用量が含まれます - 使用量が$20未満 → 追加料金なし -- 使用量が$20を超える → 月末に超過分を支払い -- 例:$35の使用量 = $20(サブスクリプション)+ $15(超過分) +- 使用量が$20超過 → 月末に超過分を支払い +- 例:使用量$35 = $20(サブスクリプション)+ $15(超過料金) -**チームプラン(席あたり月額$40):** -- チームメンバー全体でプールされた使用量 -- チーム全体の使用量から超過分を計算 -- 組織のオーナーが一括で請求を受ける +**チームプラン(1席あたり月額$40):** +- チームメンバー全員で使用量をプール +- チーム全体の使用量から超過料金を計算 +- 組織のオーナーが1つの請求書を受け取ります **エンタープライズプラン:** - 固定月額料金、超過料金なし -- 契約に基づくカスタム使用制限 +- 契約に基づくカスタム使用量制限 ### しきい値課金 -未請求の超過分が$50に達すると、Simは自動的に未請求の全額を請求します。 +未請求の超過料金が$50に達すると、Simは未請求金額の全額を自動的に請求します。 **例:** -- 10日目:$70の超過分 → 即時に$70を請求 -- 15日目:追加$35の使用(合計$105) → すでに請求済み、アクションなし -- 20日目:さらに$50の使用(合計$155、未請求$85) → 即時に$85を請求 +- 10日目:超過料金$70 → 即座に$70を請求 +- 15日目:追加使用量$35(合計$105) → すでに請求済み、アクションなし +- 20日目:さらに$50の使用量(合計$155、未請求$85) → 即座に$85を請求 -これにより、期間終了時に一度に大きな請求が発生するのではなく、月全体に大きな超過料金が分散されます。 +これにより、期間終了時の1回の大きな請求ではなく、大きな超過料金を月全体に分散させることができます。 ## コスト管理のベストプラクティス -1. **定期的な監視**: 予期せぬ事態を避けるため、使用状況ダッシュボードを頻繁に確認する -2. **予算の設定**: プランの制限を支出のガードレールとして使用する -3. **ワークフローの最適化**: コストの高い実行を見直し、プロンプトやモデル選択を最適化する -4. **適切なモデルの使用**: タスクの要件にモデルの複雑さを合わせる -5. **類似タスクのバッチ処理**: 可能な場合は複数のリクエストを組み合わせてオーバーヘッドを削減する +1. **定期的な監視**:予期しない事態を避けるため、使用状況ダッシュボードを頻繁に確認してください +2. **予算の設定**:プランの制限を支出のガードレールとして使用してください +3. **ワークフローの最適化**:コストの高い実行を確認し、プロンプトやモデルの選択を最適化してください +4. **適切なモデルの使用**:タスクの要件に合わせてモデルの複雑さを選択してください +5. **類似タスクのバッチ処理**:可能な限り複数のリクエストを組み合わせて、オーバーヘッドを削減してください ## 次のステップ - [設定 → サブスクリプション](https://sim.ai/settings/subscription)で現在の使用状況を確認する -- 実行詳細を追跡するための[ロギング](/execution/logging)について学ぶ +- 実行の詳細を追跡するための[ログ記録](/execution/logging)について学ぶ - プログラムによるコスト監視のための[外部API](/execution/api)を探索する -- コスト削減のための[ワークフロー最適化テクニック](/blocks)をチェックする \ No newline at end of file +- コストを削減するための[ワークフロー最適化テクニック](/blocks)を確認する \ No newline at end of file diff --git a/apps/docs/content/docs/ja/triggers/schedule.mdx b/apps/docs/content/docs/ja/triggers/schedule.mdx index f88d45d937..c7ffaf42c7 100644 --- a/apps/docs/content/docs/ja/triggers/schedule.mdx +++ b/apps/docs/content/docs/ja/triggers/schedule.mdx @@ -56,7 +56,7 @@ import { Image } from '@/components/ui/image' ## 自動無効化 -スケジュールは**10回連続で失敗**すると、エラーの連鎖を防ぐため自動的に無効化されます。無効化されると: +スケジュールは**100回連続で失敗**すると、エラーの連鎖を防ぐために自動的に無効化されます。無効化されると: - スケジュールブロックに警告バッジが表示されます - スケジュールの実行が停止します diff --git a/apps/docs/content/docs/zh/execution/costs.mdx b/apps/docs/content/docs/zh/execution/costs.mdx index 8787b5b0e6..27348044ec 100644 --- a/apps/docs/content/docs/zh/execution/costs.mdx +++ b/apps/docs/content/docs/zh/execution/costs.mdx @@ -105,43 +105,47 @@ totalCost = baseExecutionCharge + modelCost 显示的价格为截至 2025 年 9 月 10 日的费率。请查看提供商文档以获取最新价格。 +## 自带密钥(BYOK) + +你可以在 **设置 → BYOK** 中为托管模型(OpenAI、Anthropic、Google、Mistral)使用你自己的 API 密钥,以按基础价格计费。密钥会被加密,并在整个工作区范围内生效。 + ## 成本优化策略 -- **模型选择**:根据任务复杂性选择模型。简单任务可以使用 GPT-4.1-nano,而复杂推理可能需要 o1 或 Claude Opus。 -- **提示工程**:结构良好、简洁的提示可以减少令牌使用,同时保持质量。 -- **本地模型**:对于非关键任务,使用 Ollama 或 VLLM 完全消除 API 成本。 -- **缓存和重用**:将经常使用的结果存储在变量或文件中,以避免重复调用 AI 模型。 -- **批量处理**:在单次 AI 请求中处理多个项目,而不是逐一调用。 +- **模型选择**:根据任务复杂度选择合适的模型。简单任务可用 GPT-4.1-nano,复杂推理可选 o1 或 Claude Opus。 +- **提示工程**:结构清晰、简洁的提示能减少 token 使用量,同时保证质量。 +- **本地模型**:对于非关键任务,使用 Ollama 或 VLLM,可完全消除 API 成本。 +- **缓存与复用**:将常用结果存储在变量或文件中,避免重复调用 AI 模型。 +- **批量处理**:一次 AI 请求处理多个项目,减少单独调用次数。 ## 使用监控 -在 设置 → 订阅 中监控您的使用情况和账单: +你可以在 设置 → 订阅 中监控你的用量和账单: -- **当前使用情况**:当前周期的实时使用和成本 -- **使用限制**:计划限制及其可视化进度指示器 -- **账单详情**:预计费用和最低承诺 -- **计划管理**:升级选项和账单历史记录 +- **当前用量**:当前周期的实时用量和费用 +- **用量上限**:带有可视化进度指示的套餐限制 +- **账单明细**:预计费用和最低承诺金额 +- **套餐管理**:升级选项和账单历史 -### 程序化使用跟踪 +### 编程方式用量追踪 -您可以通过 API 程序化地查询当前的使用情况和限制: +你可以通过 API 以编程方式查询当前用量和限制: -**端点:** +**接口地址:** ```text GET /api/users/me/usage-limits ``` -**认证:** -- 在 `X-API-Key` 标头中包含您的 API 密钥 +**认证方式:** +- 在 `X-API-Key` header 中包含你的 API 密钥 -**示例请求:** +**请求示例:** ```bash curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits ``` -**示例响应:** +**响应示例:** ```json { @@ -171,70 +175,70 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt } ``` -**速率限制字段:** -- `requestsPerMinute`:持续速率限制(令牌以此速率补充) -- `maxBurst`:您可以累积的最大令牌数(突发容量) -- `remaining`:当前可用令牌数(最多可达 `maxBurst`) +**限流字段:** +- `requestsPerMinute`:持续速率限制(token 按此速率补充) +- `maxBurst`:你可累计的最大 token 数(突发容量) +- `remaining`:当前可用 token 数(最多可达 `maxBurst`) **响应字段:** -- `currentPeriodCost` 反映当前计费周期的使用情况 -- `limit` 来源于个人限制(免费/专业)或组织池限制(团队/企业) -- `plan` 是与您的用户关联的最高优先级的活动计划 +- `currentPeriodCost` 反映当前账单周期的用量 +- `limit` 来源于个人限额(Free/Pro)或组织池化限额(Team/Enterprise) +- `plan` 是与你的用户关联的最高优先级的激活套餐 -## 计划限制 +## 套餐限制 -不同的订阅计划有不同的使用限制: +不同的订阅套餐有不同的使用限制: -| 方案 | 每月使用限额 | 速率限制(每分钟) | +| 套餐 | 每月使用额度 | 速率限制(每分钟) | |------|-------------------|-------------------------| | **Free** | $20 | 5 sync,10 async | | **Pro** | $100 | 10 sync,50 async | | **Team** | $500(共享) | 50 sync,100 async | -| **Enterprise** | 定制 | 定制 | +| **Enterprise** | 自定义 | 自定义 | ## 计费模式 -Sim 使用 **基础订阅 + 超额** 的计费模式: +Sim 采用**基础订阅 + 超额**计费模式: -### 工作原理 +### 计费方式说明 -**专业计划($20/月):** -- 每月订阅包含 $20 的使用额度 -- 使用低于 $20 → 无额外费用 -- 使用超过 $20 → 月底支付超额部分 +**Pro 套餐($20/月):** +- 月度订阅包含 $20 使用额度 +- 使用未超过 $20 → 无额外费用 +- 使用超过 $20 → 月底结算超额部分 - 示例:$35 使用 = $20(订阅)+ $15(超额) -**团队计划($40/每席位/月):** -- 团队成员之间共享使用额度 -- 超额费用根据团队总使用量计算 -- 组织所有者收到一张账单 +**Team 套餐($40/人/月):** +- 团队成员共享使用额度 +- 超额费用按团队总用量计算 +- 账单由组织所有者统一支付 -**企业计划:** +**Enterprise 套餐:** - 固定月费,无超额费用 -- 根据协议自定义使用限制 +- 使用额度可按协议定制 ### 阈值计费 -当未计费的超额费用达到 $50 时,Sim 会自动计费全额未计费金额。 +当未结算的超额费用达到 $50 时,Sim 会自动结算全部未结算金额。 **示例:** -- 第 10 天:$70 超额 → 立即计费 $70 -- 第 15 天:额外使用 $35(总计 $105)→ 已计费,无需操作 -- 第 20 天:再使用 $50(总计 $155,未计费 $85)→ 立即计费 $85 +- 第 10 天:超额 $70 → 立即结算 $70 +- 第 15 天:新增 $35 使用(累计 $105)→ 已结算,无需操作 +- 第 20 天:再用 $50(累计 $155,未结算 $85)→ 立即结算 $85 -这会将大量的超额费用分散到整个月,而不是在周期结束时收到一张大账单。 +这样可以将大额超额费用分摊到每月多次结算,避免期末一次性大额账单。 ## 成本管理最佳实践 -1. **定期监控**:经常检查您的使用仪表板,避免意外情况 -2. **设定预算**:使用计划限制作为支出控制的护栏 -3. **优化工作流程**:审查高成本的执行操作,优化提示或模型选择 -4. **使用合适的模型**:根据任务需求匹配模型复杂度 -5. **批量处理相似任务**:尽可能合并多个请求以减少开销 +1. **定期监控**:经常查看用量仪表盘,避免意外支出 +2. **设置预算**:用套餐额度作为支出警戒线 +3. **优化流程**:检查高成本执行,优化提示词或模型选择 +4. **选择合适模型**:根据任务需求匹配模型复杂度 +5. **批量处理相似任务**:尽量合并请求,减少额外开销 ## 下一步 -- 在[设置 → 订阅](https://sim.ai/settings/subscription)中查看您当前的使用情况 -- 了解[日志记录](/execution/logging)以跟踪执行详情 -- 探索[外部 API](/execution/api)以进行程序化成本监控 -- 查看[工作流优化技术](/blocks)以降低成本 \ No newline at end of file +- 在 [设置 → 订阅](https://sim.ai/settings/subscription) 中查看您当前的使用情况 +- 了解 [日志记录](/execution/logging),以跟踪执行详情 +- 探索 [外部 API](/execution/api),实现程序化成本监控 +- 查看 [工作流优化技巧](/blocks),以降低成本 \ No newline at end of file diff --git a/apps/docs/content/docs/zh/triggers/schedule.mdx b/apps/docs/content/docs/zh/triggers/schedule.mdx index 84d7d2f39e..fca74cb4e7 100644 --- a/apps/docs/content/docs/zh/triggers/schedule.mdx +++ b/apps/docs/content/docs/zh/triggers/schedule.mdx @@ -56,7 +56,7 @@ import { Image } from '@/components/ui/image' ## 自动禁用 -计划在连续 **10 次失败** 后会自动禁用,以防止错误持续发生。禁用后: +为防止持续性错误,计划任务在**连续失败 100 次**后会自动禁用。禁用后: - 计划块上会显示警告徽章 - 计划将停止执行 diff --git a/apps/docs/i18n.lock b/apps/docs/i18n.lock index 06265e9b8b..91cff9ce4b 100644 --- a/apps/docs/i18n.lock +++ b/apps/docs/i18n.lock @@ -228,7 +228,7 @@ checksums: content/8: ab4fe131de634064f9a7744a11599434 content/9: 2f6c9564a33ad9f752df55840b0c8e16 content/10: fef34568e5bbd5a50e2a89412f85302c - content/11: b7ae0ecf6fbaa92b049c718720e4007e + content/11: a891bfb5cf490148001f05acde467f68 content/12: bcd95e6bef30b6f480fee33800928b13 content/13: 2ff1c8bf00c740f66bce8a4a7f768ca8 content/14: 16eb64906b9e981ea3c11525ff5a1c2e @@ -4581,39 +4581,41 @@ checksums: content/19: 83fc31418ff454a5e06b290e3708ef32 content/20: 4392b5939a6d5774fb080cad1ee1dbb8 content/21: 890b65b7326a9eeef3933a8b63f6ccdd - content/22: 892d6a80d8ac5a895a20408462f63cc5 - content/23: 930176b3786ebbe9eb1f76488f183140 - content/24: 22d9d167630c581e868d6d7a9fdddbcf - content/25: d250621762d63cd87b3359236c95bdac - content/26: 50be8ae73b8ce27de7ddd21964ee29e8 - content/27: cd622841b5bc748a7b2a0d9252e72bd5 - content/28: 38608a5d416eb33f373c6f9e6bf546b9 - content/29: 074c12c794283c3af53a3f038fbda2a6 - content/30: 5cdcf7e32294e087612b77914d850d26 - content/31: 7529829b2f064fedf956da639aaea8e1 - content/32: 7b5e2207a0d93fd434b92f2f290a8dd5 - content/33: f950b8f58af1973a3e00393d860bce02 - content/34: d5ff07fec9455183e1d93f7ddf1dab1b - content/35: 5d2d85e082d9fdd3859fb5c788d5f9a3 - content/36: 23a7de9c5adb6e07c28c23a9d4e03dc2 - content/37: 7bb928aba33a4013ad5f08487da5bbf9 - content/38: dbbf313837f13ddfa4a8843d71cb9cc4 - content/39: cf10560ae6defb8ee5da344fc6509f6e - content/40: 1dea5c6442c127ae290185db0cef067b - content/41: 332dab0588fb35dabb64b674ba6120eb - content/42: 714b3f99b0a8686bbb3434deb1f682b3 - content/43: ba18ac99184b17d7e49bd1abdc814437 - content/44: bed2b629274d55c38bd637e6a28dbc4a - content/45: 71487ae6f6fb1034d1787456de442e6d - content/46: 137d9874cf5ec8d09bd447f224cc7a7c - content/47: 6b5b4c3b2f98b8fc7dd908fef2605ce8 - content/48: 3af6812662546ce647a55939241fd88e - content/49: 6a4d7f0ccb8c28303251d1ef7b3dcca7 - content/50: 5dce779f77cc2b0abf12802a833df499 - content/51: aa47ff01b631252f024eaaae0c773e42 - content/52: 1266d1c7582bb617cdef56857be34f30 - content/53: c2cef2688104adaf6641092f43d4969a - content/54: 089fc64b4589b2eaa371de7e04c4aed9 + content/22: ada515cf6e2e0f9d3f57f720f79699d3 + content/23: 332e0d08f601da9fb56c6b7e7c8e9daf + content/24: 892d6a80d8ac5a895a20408462f63cc5 + content/25: 930176b3786ebbe9eb1f76488f183140 + content/26: 22d9d167630c581e868d6d7a9fdddbcf + content/27: d250621762d63cd87b3359236c95bdac + content/28: 50be8ae73b8ce27de7ddd21964ee29e8 + content/29: cd622841b5bc748a7b2a0d9252e72bd5 + content/30: 38608a5d416eb33f373c6f9e6bf546b9 + content/31: 074c12c794283c3af53a3f038fbda2a6 + content/32: 5cdcf7e32294e087612b77914d850d26 + content/33: 7529829b2f064fedf956da639aaea8e1 + content/34: 7b5e2207a0d93fd434b92f2f290a8dd5 + content/35: f950b8f58af1973a3e00393d860bce02 + content/36: d5ff07fec9455183e1d93f7ddf1dab1b + content/37: 5d2d85e082d9fdd3859fb5c788d5f9a3 + content/38: 23a7de9c5adb6e07c28c23a9d4e03dc2 + content/39: 7bb928aba33a4013ad5f08487da5bbf9 + content/40: dbbf313837f13ddfa4a8843d71cb9cc4 + content/41: cf10560ae6defb8ee5da344fc6509f6e + content/42: 1dea5c6442c127ae290185db0cef067b + content/43: 332dab0588fb35dabb64b674ba6120eb + content/44: 714b3f99b0a8686bbb3434deb1f682b3 + content/45: ba18ac99184b17d7e49bd1abdc814437 + content/46: bed2b629274d55c38bd637e6a28dbc4a + content/47: 71487ae6f6fb1034d1787456de442e6d + content/48: 137d9874cf5ec8d09bd447f224cc7a7c + content/49: 6b5b4c3b2f98b8fc7dd908fef2605ce8 + content/50: 3af6812662546ce647a55939241fd88e + content/51: 6a4d7f0ccb8c28303251d1ef7b3dcca7 + content/52: 5dce779f77cc2b0abf12802a833df499 + content/53: aa47ff01b631252f024eaaae0c773e42 + content/54: 1266d1c7582bb617cdef56857be34f30 + content/55: c2cef2688104adaf6641092f43d4969a + content/56: 089fc64b4589b2eaa371de7e04c4aed9 722959335ba76c9d0097860e2ad5a952: meta/title: 1f5b53b9904ec41d49c1e726e3d56b40 content/0: c2b41859d63a751682f0d9aec488e581 diff --git a/apps/docs/lib/db.ts b/apps/docs/lib/db.ts new file mode 100644 index 0000000000..9ecca9431f --- /dev/null +++ b/apps/docs/lib/db.ts @@ -0,0 +1,4 @@ +import { db } from '@sim/db' +import { docsEmbeddings } from '@sim/db/schema' + +export { db, docsEmbeddings } diff --git a/apps/docs/lib/embeddings.ts b/apps/docs/lib/embeddings.ts new file mode 100644 index 0000000000..c41a3f1989 --- /dev/null +++ b/apps/docs/lib/embeddings.ts @@ -0,0 +1,40 @@ +/** + * Generate embeddings for search queries using OpenAI API + */ +export async function generateSearchEmbedding(query: string): Promise { + const apiKey = process.env.OPENAI_API_KEY + + if (!apiKey) { + throw new Error('OPENAI_API_KEY environment variable is required') + } + + const response = await fetch('https://api.openai.com/v1/embeddings', { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + input: query, + model: 'text-embedding-3-small', + encoding_format: 'float', + }), + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`OpenAI API failed: ${response.status} ${response.statusText} - ${errorText}`) + } + + const data = await response.json() + + if (!data?.data || !Array.isArray(data.data) || data.data.length === 0) { + throw new Error('OpenAI API returned invalid response structure: missing or empty data array') + } + + if (!data.data[0]?.embedding || !Array.isArray(data.data[0].embedding)) { + throw new Error('OpenAI API returned invalid response structure: missing or invalid embedding') + } + + return data.data[0].embedding +} diff --git a/apps/docs/package.json b/apps/docs/package.json index a589e671ed..59b2610630 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -11,16 +11,19 @@ "type-check": "tsc --noEmit" }, "dependencies": { + "@sim/db": "workspace:*", "@tabler/icons-react": "^3.31.0", "@vercel/og": "^0.6.5", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "drizzle-orm": "^0.44.5", "fumadocs-core": "16.2.3", "fumadocs-mdx": "14.1.0", "fumadocs-ui": "16.2.3", "lucide-react": "^0.511.0", "next": "16.1.0-canary.21", "next-themes": "^0.4.6", + "postgres": "^3.4.5", "react": "19.2.1", "react-dom": "19.2.1", "tailwind-merge": "^3.0.2" diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx index 775bf95705..10b2313bfd 100644 --- a/apps/sim/app/(auth)/login/login-form.tsx +++ b/apps/sim/app/(auth)/login/login-form.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' @@ -18,7 +19,6 @@ import { client } from '@/lib/auth/auth-client' import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env' import { cn } from '@/lib/core/utils/cn' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index e1d2112645..29ef3e425c 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -1,9 +1,9 @@ 'use client' import { Suspense, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import { createLogger } from '@/lib/logs/console/logger' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' import { SetNewPasswordForm } from '@/app/(auth)/reset-password/reset-password-form' diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 654676f0e6..0c08283b37 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -1,6 +1,7 @@ 'use client' import { Suspense, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' @@ -10,7 +11,6 @@ import { Label } from '@/components/ui/label' import { client, useSession } from '@/lib/auth/auth-client' import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' diff --git a/apps/sim/app/(auth)/sso/sso-form.tsx b/apps/sim/app/(auth)/sso/sso-form.tsx index ae699134e6..4d01ebd0b1 100644 --- a/apps/sim/app/(auth)/sso/sso-form.tsx +++ b/apps/sim/app/(auth)/sso/sso-form.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { Button } from '@/components/ui/button' @@ -9,7 +10,6 @@ import { Label } from '@/components/ui/label' import { client } from '@/lib/auth/auth-client' import { env, isFalsy } from '@/lib/core/config/env' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts index fd30e960f3..f59c0446cd 100644 --- a/apps/sim/app/(auth)/verify/use-verification.ts +++ b/apps/sim/app/(auth)/verify/use-verification.ts @@ -1,9 +1,9 @@ 'use client' import { useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { useRouter, useSearchParams } from 'next/navigation' import { client, useSession } from '@/lib/auth/auth-client' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('useVerification') diff --git a/apps/sim/app/(landing)/actions/github.ts b/apps/sim/app/(landing)/actions/github.ts index 42f586a956..c5785f0e9c 100644 --- a/apps/sim/app/(landing)/actions/github.ts +++ b/apps/sim/app/(landing)/actions/github.ts @@ -1,4 +1,4 @@ -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' const DEFAULT_STARS = '19.4k' diff --git a/apps/sim/app/(landing)/careers/page.tsx b/apps/sim/app/(landing)/careers/page.tsx index 2f12b3d9c4..37c5f7a0e1 100644 --- a/apps/sim/app/(landing)/careers/page.tsx +++ b/apps/sim/app/(landing)/careers/page.tsx @@ -1,7 +1,8 @@ 'use client' import { useRef, useState } from 'react' -import { Loader2, X } from 'lucide-react' +import { createLogger } from '@sim/logger' +import { X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -15,7 +16,6 @@ import { import { Textarea } from '@/components/ui/textarea' import { isHosted } from '@/lib/core/config/feature-flags' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { soehne } from '@/app/_styles/fonts/soehne/soehne' import Footer from '@/app/(landing)/components/footer/footer' @@ -499,16 +499,11 @@ export default function CareersPage() { className='min-w-[200px] rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all duration-300 hover:opacity-90 disabled:opacity-50' size='lg' > - {isSubmitting ? ( - <> - - Submitting... - - ) : submitStatus === 'success' ? ( - 'Submitted' - ) : ( - 'Submit Application' - )} + {isSubmitting + ? 'Submitting...' + : submitStatus === 'success' + ? 'Submitted' + : 'Submit Application'} diff --git a/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx b/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx index 6df35cdc12..2cc62e9860 100644 --- a/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx +++ b/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx @@ -1,6 +1,8 @@ 'use client' +import type { ComponentType, SVGProps } from 'react' import { useState } from 'react' +import { createLogger } from '@sim/logger' import type { LucideIcon } from 'lucide-react' import { ArrowRight, @@ -13,7 +15,6 @@ import { } from 'lucide-react' import { useRouter } from 'next/navigation' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { inter } from '@/app/_styles/fonts/inter/inter' import { ENTERPRISE_PLAN_FEATURES, @@ -24,7 +25,7 @@ import { const logger = createLogger('LandingPricing') interface PricingFeature { - icon: LucideIcon + icon: LucideIcon | ComponentType> text: string } diff --git a/apps/sim/app/(landing)/components/nav/nav.tsx b/apps/sim/app/(landing)/components/nav/nav.tsx index b4ce6ddbe9..d8ae4b9065 100644 --- a/apps/sim/app/(landing)/components/nav/nav.tsx +++ b/apps/sim/app/(landing)/components/nav/nav.tsx @@ -1,6 +1,7 @@ 'use client' import { useCallback, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { ArrowRight, ChevronRight } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' @@ -8,7 +9,6 @@ import { useRouter } from 'next/navigation' import { GithubIcon } from '@/components/icons' import { useBrandConfig } from '@/lib/branding/branding' import { isHosted } from '@/lib/core/config/feature-flags' -import { createLogger } from '@/lib/logs/console/logger' import { soehne } from '@/app/_styles/fonts/soehne/soehne' import { getFormattedGitHubStars } from '@/app/(landing)/actions/github' @@ -20,7 +20,7 @@ interface NavProps { } export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) { - const [githubStars, setGithubStars] = useState('24k') + const [githubStars, setGithubStars] = useState('24.4k') const [isHovered, setIsHovered] = useState(false) const [isLoginHovered, setIsLoginHovered] = useState(false) const router = useRouter() diff --git a/apps/sim/app/_shell/hydration-error-handler.tsx b/apps/sim/app/_shell/hydration-error-handler.tsx index 8eae512e41..56050875f0 100644 --- a/apps/sim/app/_shell/hydration-error-handler.tsx +++ b/apps/sim/app/_shell/hydration-error-handler.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect } from 'react' -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' const logger = createLogger('RootLayout') diff --git a/apps/sim/app/_shell/providers/theme-provider.tsx b/apps/sim/app/_shell/providers/theme-provider.tsx index a6bc5444e0..cbb31e4423 100644 --- a/apps/sim/app/_shell/providers/theme-provider.tsx +++ b/apps/sim/app/_shell/providers/theme-provider.tsx @@ -7,7 +7,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes' export function ThemeProvider({ children, ...props }: ThemeProviderProps) { const pathname = usePathname() - // Force light mode on public/marketing pages, dark mode everywhere else + // Force light mode on public/marketing pages, allow user preference elsewhere const isLightModePage = pathname === '/' || pathname.startsWith('/login') || @@ -27,10 +27,10 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) { {children} diff --git a/apps/sim/app/_shell/zoom-prevention.tsx b/apps/sim/app/_shell/zoom-prevention.tsx deleted file mode 100644 index 66f0ceb74b..0000000000 --- a/apps/sim/app/_shell/zoom-prevention.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client' - -import { useEffect } from 'react' - -export function ZoomPrevention() { - useEffect(() => { - const preventZoom = (e: KeyboardEvent | WheelEvent) => { - // Prevent zoom on ctrl/cmd + wheel - if (e instanceof WheelEvent && (e.ctrlKey || e.metaKey)) { - e.preventDefault() - } - - // Prevent zoom on ctrl/cmd + plus/minus/zero - if (e instanceof KeyboardEvent && (e.ctrlKey || e.metaKey)) { - if (e.key === '=' || e.key === '-' || e.key === '0') { - e.preventDefault() - } - } - } - - // Add event listeners - document.addEventListener('wheel', preventZoom, { passive: false }) - document.addEventListener('keydown', preventZoom) - - // Cleanup - return () => { - document.removeEventListener('wheel', preventZoom) - document.removeEventListener('keydown', preventZoom) - } - }, []) - - return null -} diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index f67e27e375..8915151130 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -8,7 +8,7 @@ */ :root { --sidebar-width: 232px; - --panel-width: 244px; + --panel-width: 260px; --toolbar-triggers-height: 300px; --editor-connections-height: 200px; --terminal-height: 196px; @@ -26,41 +26,6 @@ height: var(--terminal-height); } -/** - * Workflow component z-index fixes and background colors - */ -.workflow-container .react-flow__edges { - z-index: 0 !important; -} - -.workflow-container .react-flow__node { - z-index: 21 !important; -} - -.workflow-container .react-flow__handle { - z-index: 30 !important; -} - -.workflow-container .react-flow__edge [data-testid="workflow-edge"] { - z-index: 0 !important; -} - -.workflow-container .react-flow__edge-labels { - z-index: 60 !important; -} - -.workflow-container, -.workflow-container .react-flow__pane, -.workflow-container .react-flow__renderer { - background-color: var(--bg) !important; -} - -.dark .workflow-container, -.dark .workflow-container .react-flow__pane, -.dark .workflow-container .react-flow__renderer { - background-color: var(--bg) !important; -} - /** * Landing loop animation styles (keyframes defined in tailwind.config.ts) */ @@ -75,101 +40,87 @@ } /** - * Dark color tokens - single source of truth for all colors (dark-only) + * Color tokens - single source of truth for all colors + * Light mode: Warm theme + * Dark mode: Dark neutral theme */ @layer base { :root, .light { - /* Neutrals (surfaces) - shadcn stone palette */ - --bg: #ffffff; /* pure white for landing/auth pages */ - --surface-1: #fafaf9; /* stone-50 */ - --surface-2: #ffffff; /* white */ - --surface-3: #f5f5f4; /* stone-100 */ - --surface-4: #f5f5f4; /* stone-100 */ - --surface-5: #eeedec; /* stone-150 */ - --surface-6: #f5f5f4; /* stone-100 */ - --surface-9: #f5f5f4; /* stone-100 */ - --surface-11: #e7e5e4; /* stone-200 */ - --surface-12: #d6d3d1; /* stone-300 */ - --surface-13: #a8a29e; /* stone-400 */ - --surface-14: #78716c; /* stone-500 */ - --surface-15: #57534e; /* stone-600 */ - --surface-elevated: #ffffff; /* white */ - --bg-strong: #e7e5e4; /* stone-200 */ - - /* Text - shadcn stone palette for proper contrast */ - --text-primary: #1c1917; /* stone-900 */ - --text-secondary: #292524; /* stone-800 */ - --text-tertiary: #57534e; /* stone-600 */ - --text-muted: #78716c; /* stone-500 */ - --text-subtle: #a8a29e; /* stone-400 */ - --text-inverse: #fafaf9; /* stone-50 */ - --text-error: #dc2626; - - /* Borders / dividers - shadcn stone palette */ - --border: #d6d3d1; /* stone-300 */ - --border-strong: #d6d3d1; /* stone-300 */ - --divider: #e7e5e4; /* stone-200 */ - --border-muted: #e7e5e4; /* stone-200 */ - --border-success: #d6d3d1; /* stone-300 */ + --bg: #f9faf8; /* main canvas - near white */ + --surface-1: #f9faf8; /* sidebar, panels - light warm gray */ + --surface-2: #fdfdfb; /* blocks, cards, modals - soft warm white */ + --surface-3: #f4f5f1; /* popovers, headers - more contrast */ + --surface-4: #f2f3ef; /* buttons base */ + --border: #d7dcda; /* primary border */ + --surface-5: #f0f1ed; /* inputs, form elements - subtle */ + --border-1: #d7dcda; /* stronger border - sage gray */ + --surface-6: #eceee9; /* popovers, elevated surfaces */ + --surface-7: #e8e9e4; + + --workflow-edge: #d7dcda; /* workflow handles/edges - matches border-1 */ + + /* Text - warm neutrals */ + --text-primary: #2d2d2d; + --text-secondary: #404040; + --text-tertiary: #5c5c5c; + --text-muted: #737373; + --text-subtle: #8c8c8c; + --text-inverse: #f0fff6; + --text-error: #ef4444; + + /* Borders / dividers */ + --divider: #e8e9e4; + --border-muted: #dfe0db; + --border-success: #d7dcda; /* Brand & state */ --brand-400: #8e4cfb; - --brand-500: #6f3dfa; --brand-secondary: #33b4ff; --brand-tertiary: #22c55e; - --brand-tertiary-2: #33c481; + --brand-tertiary-2: #32bd7e; --warning: #ea580c; /* Utility */ --white: #ffffff; - /* Font weights - lighter for light mode (-20 from dark) */ + /* Font weights - lighter for light mode */ --font-weight-base: 430; --font-weight-medium: 450; --font-weight-semibold: 500; - /* RGB for opacity usage - stone palette */ - --surface-4-rgb: 245 245 244; /* stone-100 */ - --surface-5-rgb: 238 237 236; /* stone-150 */ - --surface-7-rgb: 245 245 244; /* stone-100 */ - --surface-9-rgb: 245 245 244; /* stone-100 */ - --divider-rgb: 231 229 228; /* stone-200 */ - --white-rgb: 255 255 255; - --black-rgb: 0 0 0; - - /* Extended palette - mapped to shadcn stone palette */ - --c-0D0D0D: #0c0a09; /* stone-950 */ - --c-1A1A1A: #1c1917; /* stone-900 */ - --c-1F1F1F: #1c1917; /* stone-900 */ - --c-2A2A2A: #292524; /* stone-800 */ - --c-383838: #44403c; /* stone-700 */ - --c-414141: #57534e; /* stone-600 */ + /* Extended palette */ + --c-0D0D0D: #0d0d0d; + --c-1A1A1A: #1a1a1a; + --c-1F1F1F: #1f1f1f; + --c-2A2A2A: #2a2a2a; + --c-383838: #383838; + --c-414141: #414141; --c-442929: #442929; --c-491515: #491515; - --c-575757: #78716c; /* stone-500 */ - --c-686868: #78716c; /* stone-500 */ - --c-707070: #78716c; /* stone-500 */ - --c-727272: #78716c; /* stone-500 */ - --c-737373: #78716c; /* stone-500 */ - --c-808080: #a8a29e; /* stone-400 */ - --c-858585: #a8a29e; /* stone-400 */ - --c-868686: #a8a29e; /* stone-400 */ - --c-8D8D8D: #a8a29e; /* stone-400 */ - --c-939393: #a8a29e; /* stone-400 */ - --c-A8A8A8: #a8a29e; /* stone-400 */ - --c-B8B8B8: #d6d3d1; /* stone-300 */ - --c-C0C0C0: #d6d3d1; /* stone-300 */ - --c-CDCDCD: #d6d3d1; /* stone-300 */ - --c-D0D0D0: #d6d3d1; /* stone-300 */ - --c-D2D2D2: #d6d3d1; /* stone-300 */ - --c-E0E0E0: #e7e5e4; /* stone-200 */ - --c-E5E5E5: #e7e5e4; /* stone-200 */ - --c-E8E8E8: #e7e5e4; /* stone-200 */ - --c-EEEEEE: #f5f5f4; /* stone-100 */ - --c-F0F0F0: #f5f5f4; /* stone-100 */ - --c-F4F4F4: #fafaf9; /* stone-50 */ - --c-F5F5F5: #fafaf9; /* stone-50 */ + --c-575757: #575757; + --c-686868: #686868; + --c-707070: #707070; + --c-727272: #727272; + --c-737373: #737373; + --c-808080: #808080; + --c-858585: #858585; + --c-868686: #868686; + --c-8D8D8D: #8d8d8d; + --c-939393: #939393; + --c-A8A8A8: #a8a8a8; + --c-B8B8B8: #b8b8b8; + --c-C0C0C0: #c0c0c0; + --c-CDCDCD: #cdcdcd; + --c-D0D0D0: #d0d0d0; + --c-D2D2D2: #d2d2d2; + --c-E0E0E0: #e0e0e0; + --c-E5E5E5: #e5e5e5; + --c-E8E8E8: #e8e8e8; + --c-EEEEEE: #eeeeee; + --c-F0F0F0: #f0f0f0; + --c-F4F4F4: #f4f4f4; + --c-F5F5F5: #f5f5f5; /* Blues and cyans */ --c-00B0B0: #00b0b0; @@ -203,30 +154,27 @@ /* Terminal status badges */ --terminal-status-error-bg: #feeeee; --terminal-status-error-border: #f87171; - --terminal-status-info-bg: #f5f5f4; /* stone-100 */ - --terminal-status-info-border: #a8a29e; /* stone-400 */ - --terminal-status-info-color: #57534e; /* stone-600 */ + --terminal-status-info-bg: #f5f5f4; + --terminal-status-info-border: #a8a29e; + --terminal-status-info-color: #57534e; --terminal-status-warning-bg: #fef9e7; --terminal-status-warning-border: #f5c842; --terminal-status-warning-color: #a16207; } .dark { - /* Neutrals (surfaces) */ + /* Surface */ --bg: #1b1b1b; --surface-1: #1e1e1e; --surface-2: #232323; --surface-3: #242424; - --surface-4: #252525; - --surface-5: #272727; - --surface-6: #282828; - --surface-9: #363636; - --surface-11: #3d3d3d; - --surface-12: #434343; - --surface-13: #454545; - --surface-14: #4a4a4a; - --surface-15: #5a5a5a; - --surface-elevated: #202020; - --bg-strong: #0c0c0c; + --surface-4: #292929; + --border: #2c2c2c; + --surface-5: #363636; + --border-1: #3d3d3d; + --surface-6: #454545; + --surface-7: #454545; + + --workflow-edge: #454545; /* workflow handles/edges - same as surface-6 in dark */ /* Text */ --text-primary: #e6e6e6; @@ -237,9 +185,7 @@ --text-inverse: #1b1b1b; --text-error: #ef4444; - /* Borders / dividers */ - --border: #2c2c2c; - --border-strong: #303030; + /* --border-strong: #303030; */ --divider: #393939; --border-muted: #424242; --border-success: #575757; @@ -248,7 +194,7 @@ --brand-400: #8e4cfb; --brand-secondary: #33b4ff; --brand-tertiary: #22c55e; - --brand-tertiary-2: #33c481; + --brand-tertiary-2: #32bd7e; --warning: #ff6600; /* Utility */ @@ -259,15 +205,6 @@ --font-weight-medium: 480; --font-weight-semibold: 550; - /* RGB for opacity usage */ - --surface-4-rgb: 37 37 37; - --surface-5-rgb: 39 39 39; - --surface-7-rgb: 44 44 44; - --surface-9-rgb: 54 54 54; - --divider-rgb: 57 57 57; - --white-rgb: 255 255 255; - --black-rgb: 0 0 0; - /* Extended palette (exhaustive from code usage via -[#...]) */ /* Neutral deep shades */ --c-0D0D0D: #0d0d0d; @@ -395,34 +332,34 @@ } ::-webkit-scrollbar-thumb { - background-color: var(--surface-12); + background-color: var(--surface-7); border-radius: var(--radius); } ::-webkit-scrollbar-thumb:hover { - background-color: var(--surface-13); + background-color: var(--surface-7); } /* Dark Mode Global Scrollbar */ .dark ::-webkit-scrollbar-track { - background: var(--surface-5); + background: var(--surface-4); } .dark ::-webkit-scrollbar-thumb { - background-color: var(--surface-12); + background-color: var(--surface-7); } .dark ::-webkit-scrollbar-thumb:hover { - background-color: var(--surface-13); + background-color: var(--surface-7); } * { scrollbar-width: thin; - scrollbar-color: var(--surface-12) var(--surface-1); + scrollbar-color: var(--surface-7) var(--surface-1); } .dark * { - scrollbar-color: var(--surface-12) var(--surface-5); + scrollbar-color: var(--surface-7) var(--surface-4); } .copilot-scrollable { @@ -438,8 +375,8 @@ } .panel-tab-active { - background-color: var(--white); - color: var(--text-inverse); + background-color: var(--surface-5); + color: var(--text-primary); border-color: var(--border-muted); } @@ -450,7 +387,7 @@ } .panel-tab-inactive:hover { - background-color: var(--surface-9); + background-color: var(--surface-5); color: var(--text-primary); } @@ -642,7 +579,7 @@ input[type="search"]::-ms-clear { } html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="copilot"] { - background-color: var(--surface-11) !important; + background-color: var(--border-1) !important; color: var(--text-primary) !important; } html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="toolbar"], @@ -652,7 +589,7 @@ input[type="search"]::-ms-clear { } html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="toolbar"] { - background-color: var(--surface-11) !important; + background-color: var(--border-1) !important; color: var(--text-primary) !important; } html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="copilot"], @@ -662,7 +599,7 @@ input[type="search"]::-ms-clear { } html[data-panel-active-tab="editor"] .panel-container [data-tab-button="editor"] { - background-color: var(--surface-11) !important; + background-color: var(--border-1) !important; color: var(--text-primary) !important; } html[data-panel-active-tab="editor"] .panel-container [data-tab-button="copilot"], diff --git a/apps/sim/app/api/__test-utils__/utils.ts b/apps/sim/app/api/__test-utils__/utils.ts index 8b85d79dc5..3ecefb443c 100644 --- a/apps/sim/app/api/__test-utils__/utils.ts +++ b/apps/sim/app/api/__test-utils__/utils.ts @@ -1,6 +1,9 @@ +import { createMockLogger as createSimTestingMockLogger } from '@sim/testing' import { NextRequest } from 'next/server' import { vi } from 'vitest' +export { createMockLogger } from '@sim/testing' + export interface MockUser { id: string email: string @@ -214,12 +217,11 @@ export const mockDb = { })), } -export const mockLogger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), -} +/** + * Mock logger using @sim/testing createMockLogger. + * This provides a consistent mock logger across all API tests. + */ +export const mockLogger = createSimTestingMockLogger() export const mockUser = { id: 'user-123', @@ -729,10 +731,11 @@ export function mockKnowledgeSchemas() { } /** - * Mock console logger + * Mock console logger using the shared mockLogger instance. + * This ensures tests can assert on the same mockLogger instance exported from this module. */ export function mockConsoleLogger() { - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) } diff --git a/apps/sim/app/api/auth/accounts/route.ts b/apps/sim/app/api/auth/accounts/route.ts index 5739586c38..a51d8585c2 100644 --- a/apps/sim/app/api/auth/accounts/route.ts +++ b/apps/sim/app/api/auth/accounts/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('AuthAccountsAPI') diff --git a/apps/sim/app/api/auth/forget-password/route.test.ts b/apps/sim/app/api/auth/forget-password/route.test.ts index 8dc57e18e3..36cbb3e0e8 100644 --- a/apps/sim/app/api/auth/forget-password/route.test.ts +++ b/apps/sim/app/api/auth/forget-password/route.test.ts @@ -162,7 +162,7 @@ describe('Forget Password API Route', () => { expect(response.status).toBe(500) expect(data.message).toBe(errorMessage) - const logger = await import('@/lib/logs/console/logger') + const logger = await import('@sim/logger') const mockLogger = logger.createLogger('ForgetPasswordTest') expect(mockLogger.error).toHaveBeenCalledWith('Error requesting password reset:', { error: expect.any(Error), @@ -192,7 +192,7 @@ describe('Forget Password API Route', () => { expect(response.status).toBe(500) expect(data.message).toBe('Failed to send password reset email. Please try again later.') - const logger = await import('@/lib/logs/console/logger') + const logger = await import('@sim/logger') const mockLogger = logger.createLogger('ForgetPasswordTest') expect(mockLogger.error).toHaveBeenCalled() }) diff --git a/apps/sim/app/api/auth/forget-password/route.ts b/apps/sim/app/api/auth/forget-password/route.ts index 6844c40c61..e8f05ecfcf 100644 --- a/apps/sim/app/api/auth/forget-password/route.ts +++ b/apps/sim/app/api/auth/forget-password/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { auth } from '@/lib/auth' import { isSameOrigin } from '@/lib/core/utils/validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth/connections/route.test.ts b/apps/sim/app/api/auth/oauth/connections/route.test.ts index f3aceda583..35bdcbc152 100644 --- a/apps/sim/app/api/auth/oauth/connections/route.test.ts +++ b/apps/sim/app/api/auth/oauth/connections/route.test.ts @@ -4,7 +4,7 @@ * @vitest-environment node */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createMockRequest } from '@/app/api/__test-utils__/utils' +import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils' describe('OAuth Connections API Route', () => { const mockGetSession = vi.fn() @@ -14,12 +14,7 @@ describe('OAuth Connections API Route', () => { where: vi.fn().mockReturnThis(), limit: vi.fn(), } - const mockLogger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - } + const mockLogger = createMockLogger() const mockParseProvider = vi.fn() const mockEvaluateScopeCoverage = vi.fn() @@ -51,7 +46,7 @@ describe('OAuth Connections API Route', () => { jwtDecode: vi.fn(), })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) diff --git a/apps/sim/app/api/auth/oauth/connections/route.ts b/apps/sim/app/api/auth/oauth/connections/route.ts index 783f3d2ce2..148f4b20f2 100644 --- a/apps/sim/app/api/auth/oauth/connections/route.ts +++ b/apps/sim/app/api/auth/oauth/connections/route.ts @@ -1,10 +1,10 @@ import { account, db, user } from '@sim/db' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { jwtDecode } from 'jwt-decode' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import type { OAuthProvider } from '@/lib/oauth' import { evaluateScopeCoverage, parseProvider } from '@/lib/oauth' diff --git a/apps/sim/app/api/auth/oauth/credentials/route.test.ts b/apps/sim/app/api/auth/oauth/credentials/route.test.ts index 1e0a2889a0..93aceaccc1 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.test.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.test.ts @@ -6,6 +6,7 @@ import { NextRequest } from 'next/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { createMockLogger } from '@/app/api/__test-utils__/utils' describe('OAuth Credentials API Route', () => { const mockGetSession = vi.fn() @@ -17,12 +18,7 @@ describe('OAuth Credentials API Route', () => { where: vi.fn().mockReturnThis(), limit: vi.fn(), } - const mockLogger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - } + const mockLogger = createMockLogger() const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef' @@ -65,7 +61,7 @@ describe('OAuth Credentials API Route', () => { jwtDecode: vi.fn(), })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) diff --git a/apps/sim/app/api/auth/oauth/credentials/route.ts b/apps/sim/app/api/auth/oauth/credentials/route.ts index 04f5e9c5ba..76a71b2df9 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { account, user, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { jwtDecode } from 'jwt-decode' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { evaluateScopeCoverage, type OAuthProvider, parseProvider } from '@/lib/oauth' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts index deeabb89dd..9cd956fee6 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts @@ -4,7 +4,7 @@ * @vitest-environment node */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createMockRequest } from '@/app/api/__test-utils__/utils' +import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils' describe('OAuth Disconnect API Route', () => { const mockGetSession = vi.fn() @@ -12,12 +12,7 @@ describe('OAuth Disconnect API Route', () => { delete: vi.fn().mockReturnThis(), where: vi.fn(), } - const mockLogger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - } + const mockLogger = createMockLogger() const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef' @@ -47,7 +42,7 @@ describe('OAuth Disconnect API Route', () => { or: vi.fn((...conditions) => ({ conditions, type: 'or' })), })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) }) diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.ts b/apps/sim/app/api/auth/oauth/disconnect/route.ts index 39f3b8648b..5050e86172 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, like, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth/microsoft/file/route.ts b/apps/sim/app/api/auth/oauth/microsoft/file/route.ts index 4bb6de84ce..af9d5d47e8 100644 --- a/apps/sim/app/api/auth/oauth/microsoft/file/route.ts +++ b/apps/sim/app/api/auth/oauth/microsoft/file/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth/microsoft/files/route.ts b/apps/sim/app/api/auth/oauth/microsoft/files/route.ts index a5fe878875..1a689b808d 100644 --- a/apps/sim/app/api/auth/oauth/microsoft/files/route.ts +++ b/apps/sim/app/api/auth/oauth/microsoft/files/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth/token/route.test.ts b/apps/sim/app/api/auth/oauth/token/route.test.ts index ed4838710c..4d22039777 100644 --- a/apps/sim/app/api/auth/oauth/token/route.test.ts +++ b/apps/sim/app/api/auth/oauth/token/route.test.ts @@ -4,7 +4,7 @@ * @vitest-environment node */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createMockRequest } from '@/app/api/__test-utils__/utils' +import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils' describe('OAuth Token API Routes', () => { const mockGetUserId = vi.fn() @@ -13,12 +13,7 @@ describe('OAuth Token API Routes', () => { const mockAuthorizeCredentialUse = vi.fn() const mockCheckHybridAuth = vi.fn() - const mockLogger = { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - } + const mockLogger = createMockLogger() const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef' const mockRequestId = mockUUID.slice(0, 8) @@ -36,7 +31,7 @@ describe('OAuth Token API Routes', () => { refreshTokenIfNeeded: mockRefreshTokenIfNeeded, })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue(mockLogger), })) diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index b89aff1aa9..603f5c6b0b 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -1,15 +1,17 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getCredential, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' const logger = createLogger('OAuthTokenAPI') +const SALESFORCE_INSTANCE_URL_REGEX = /__sf_instance__:([^\s]+)/ + const tokenRequestSchema = z.object({ credentialId: z .string({ required_error: 'Credential ID is required' }) @@ -78,10 +80,20 @@ export async function POST(request: NextRequest) { try { // Refresh the token if needed const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId) + + let instanceUrl: string | undefined + if (credential.providerId === 'salesforce' && credential.scope) { + const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX) + if (instanceMatch) { + instanceUrl = instanceMatch[1] + } + } + return NextResponse.json( { accessToken, idToken: credential.idToken || undefined, + ...(instanceUrl && { instanceUrl }), }, { status: 200 } ) @@ -147,10 +159,21 @@ export async function GET(request: NextRequest) { try { const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId) + + // For Salesforce, extract instanceUrl from the scope field + let instanceUrl: string | undefined + if (credential.providerId === 'salesforce' && credential.scope) { + const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX) + if (instanceMatch) { + instanceUrl = instanceMatch[1] + } + } + return NextResponse.json( { accessToken, idToken: credential.idToken || undefined, + ...(instanceUrl && { instanceUrl }), }, { status: 200 } ) diff --git a/apps/sim/app/api/auth/oauth/utils.test.ts b/apps/sim/app/api/auth/oauth/utils.test.ts index f53402f5b5..e144221a80 100644 --- a/apps/sim/app/api/auth/oauth/utils.test.ts +++ b/apps/sim/app/api/auth/oauth/utils.test.ts @@ -3,9 +3,11 @@ * * @vitest-environment node */ + +import { createSession, loggerMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -const mockSession = { user: { id: 'test-user-id' } } +const mockSession = createSession({ userId: 'test-user-id' }) const mockGetSession = vi.fn() vi.mock('@/lib/auth', () => ({ @@ -29,14 +31,7 @@ vi.mock('@/lib/oauth/oauth', () => ({ OAUTH_PROVIDERS: {}, })) -vi.mock('@/lib/logs/console/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }), -})) +vi.mock('@sim/logger', () => loggerMock) import { db } from '@sim/db' import { refreshOAuthToken } from '@/lib/oauth' @@ -47,14 +42,14 @@ import { refreshTokenIfNeeded, } from '@/app/api/auth/oauth/utils' -const mockDb = db as any +const mockDbTyped = db as any const mockRefreshOAuthToken = refreshOAuthToken as any describe('OAuth Utils', () => { beforeEach(() => { vi.clearAllMocks() mockGetSession.mockResolvedValue(mockSession) - mockDb.limit.mockReturnValue([]) + mockDbTyped.limit.mockReturnValue([]) }) afterEach(() => { @@ -69,14 +64,14 @@ describe('OAuth Utils', () => { }) it('should get user ID from workflow when workflowId is provided', async () => { - mockDb.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }]) + mockDbTyped.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }]) const userId = await getUserId('request-id', 'workflow-id') - expect(mockDb.select).toHaveBeenCalled() - expect(mockDb.from).toHaveBeenCalled() - expect(mockDb.where).toHaveBeenCalled() - expect(mockDb.limit).toHaveBeenCalledWith(1) + expect(mockDbTyped.select).toHaveBeenCalled() + expect(mockDbTyped.from).toHaveBeenCalled() + expect(mockDbTyped.where).toHaveBeenCalled() + expect(mockDbTyped.limit).toHaveBeenCalledWith(1) expect(userId).toBe('workflow-owner-id') }) @@ -89,7 +84,7 @@ describe('OAuth Utils', () => { }) it('should return undefined if workflow is not found', async () => { - mockDb.limit.mockReturnValueOnce([]) + mockDbTyped.limit.mockReturnValueOnce([]) const userId = await getUserId('request-id', 'nonexistent-workflow-id') @@ -100,20 +95,20 @@ describe('OAuth Utils', () => { describe('getCredential', () => { it('should return credential when found', async () => { const mockCredential = { id: 'credential-id', userId: 'test-user-id' } - mockDb.limit.mockReturnValueOnce([mockCredential]) + mockDbTyped.limit.mockReturnValueOnce([mockCredential]) const credential = await getCredential('request-id', 'credential-id', 'test-user-id') - expect(mockDb.select).toHaveBeenCalled() - expect(mockDb.from).toHaveBeenCalled() - expect(mockDb.where).toHaveBeenCalled() - expect(mockDb.limit).toHaveBeenCalledWith(1) + expect(mockDbTyped.select).toHaveBeenCalled() + expect(mockDbTyped.from).toHaveBeenCalled() + expect(mockDbTyped.where).toHaveBeenCalled() + expect(mockDbTyped.limit).toHaveBeenCalledWith(1) expect(credential).toEqual(mockCredential) }) it('should return undefined when credential is not found', async () => { - mockDb.limit.mockReturnValueOnce([]) + mockDbTyped.limit.mockReturnValueOnce([]) const credential = await getCredential('request-id', 'nonexistent-id', 'test-user-id') @@ -127,7 +122,7 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'valid-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour in the future + accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), providerId: 'google', } @@ -142,7 +137,7 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'expired-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past + accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), providerId: 'google', } @@ -155,8 +150,8 @@ describe('OAuth Utils', () => { const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id') expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token') - expect(mockDb.update).toHaveBeenCalled() - expect(mockDb.set).toHaveBeenCalled() + expect(mockDbTyped.update).toHaveBeenCalled() + expect(mockDbTyped.set).toHaveBeenCalled() expect(result).toEqual({ accessToken: 'new-token', refreshed: true }) }) @@ -165,7 +160,7 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'expired-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past + accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), providerId: 'google', } @@ -181,7 +176,7 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'token', refreshToken: null, - accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past + accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), providerId: 'google', } @@ -198,11 +193,11 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'valid-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour in the future + accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), providerId: 'google', userId: 'test-user-id', } - mockDb.limit.mockReturnValueOnce([mockCredential]) + mockDbTyped.limit.mockReturnValueOnce([mockCredential]) const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id') @@ -215,11 +210,11 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'expired-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past + accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), providerId: 'google', userId: 'test-user-id', } - mockDb.limit.mockReturnValueOnce([mockCredential]) + mockDbTyped.limit.mockReturnValueOnce([mockCredential]) mockRefreshOAuthToken.mockResolvedValueOnce({ accessToken: 'new-token', @@ -230,13 +225,13 @@ describe('OAuth Utils', () => { const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id') expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token') - expect(mockDb.update).toHaveBeenCalled() - expect(mockDb.set).toHaveBeenCalled() + expect(mockDbTyped.update).toHaveBeenCalled() + expect(mockDbTyped.set).toHaveBeenCalled() expect(token).toBe('new-token') }) it('should return null if credential not found', async () => { - mockDb.limit.mockReturnValueOnce([]) + mockDbTyped.limit.mockReturnValueOnce([]) const token = await refreshAccessTokenIfNeeded('nonexistent-id', 'test-user-id', 'request-id') @@ -248,11 +243,11 @@ describe('OAuth Utils', () => { id: 'credential-id', accessToken: 'expired-token', refreshToken: 'refresh-token', - accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past + accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), providerId: 'google', userId: 'test-user-id', } - mockDb.limit.mockReturnValueOnce([mockCredential]) + mockDbTyped.limit.mockReturnValueOnce([mockCredential]) mockRefreshOAuthToken.mockResolvedValueOnce(null) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 85b63961d1..cb9176e989 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { account, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { refreshOAuthToken } from '@/lib/oauth' const logger = createLogger('OAuthUtilsAPI') diff --git a/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts b/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts index 4337033779..61fc0b591d 100644 --- a/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts +++ b/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts b/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts index 83ba588ba2..e276111762 100644 --- a/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts +++ b/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/oauth2/callback/shopify/route.ts b/apps/sim/app/api/auth/oauth2/callback/shopify/route.ts index f1e0e24e42..b58fe329c7 100644 --- a/apps/sim/app/api/auth/oauth2/callback/shopify/route.ts +++ b/apps/sim/app/api/auth/oauth2/callback/shopify/route.ts @@ -1,9 +1,9 @@ import crypto from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ShopifyCallback') diff --git a/apps/sim/app/api/auth/oauth2/shopify/store/route.ts b/apps/sim/app/api/auth/oauth2/shopify/store/route.ts index ddc70d7d13..cf7aef92a6 100644 --- a/apps/sim/app/api/auth/oauth2/shopify/store/route.ts +++ b/apps/sim/app/api/auth/oauth2/shopify/store/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { safeAccountInsert } from '@/app/api/auth/oauth/utils' const logger = createLogger('ShopifyStore') diff --git a/apps/sim/app/api/auth/reset-password/route.test.ts b/apps/sim/app/api/auth/reset-password/route.test.ts index 58b9666448..9c9f2df5f9 100644 --- a/apps/sim/app/api/auth/reset-password/route.test.ts +++ b/apps/sim/app/api/auth/reset-password/route.test.ts @@ -148,7 +148,7 @@ describe('Reset Password API Route', () => { expect(response.status).toBe(500) expect(data.message).toBe(errorMessage) - const logger = await import('@/lib/logs/console/logger') + const logger = await import('@sim/logger') const mockLogger = logger.createLogger('PasswordResetAPI') expect(mockLogger.error).toHaveBeenCalledWith('Error during password reset:', { error: expect.any(Error), @@ -181,7 +181,7 @@ describe('Reset Password API Route', () => { 'Failed to reset password. Please try again or request a new reset link.' ) - const logger = await import('@/lib/logs/console/logger') + const logger = await import('@sim/logger') const mockLogger = logger.createLogger('PasswordResetAPI') expect(mockLogger.error).toHaveBeenCalled() }) diff --git a/apps/sim/app/api/auth/reset-password/route.ts b/apps/sim/app/api/auth/reset-password/route.ts index 6d3fe9197f..0caa1494f2 100644 --- a/apps/sim/app/api/auth/reset-password/route.ts +++ b/apps/sim/app/api/auth/reset-password/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { auth } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/auth/shopify/authorize/route.ts b/apps/sim/app/api/auth/shopify/authorize/route.ts index aa06b2c7b4..daa6dfecf0 100644 --- a/apps/sim/app/api/auth/shopify/authorize/route.ts +++ b/apps/sim/app/api/auth/shopify/authorize/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ShopifyAuthorize') diff --git a/apps/sim/app/api/auth/sso/providers/route.ts b/apps/sim/app/api/auth/sso/providers/route.ts index 09e07de745..f35f25ee2a 100644 --- a/apps/sim/app/api/auth/sso/providers/route.ts +++ b/apps/sim/app/api/auth/sso/providers/route.ts @@ -1,8 +1,8 @@ import { db, ssoProvider } from '@sim/db' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SSO-Providers') diff --git a/apps/sim/app/api/auth/sso/register/route.ts b/apps/sim/app/api/auth/sso/register/route.ts index 185273dbb8..2743842136 100644 --- a/apps/sim/app/api/auth/sso/register/route.ts +++ b/apps/sim/app/api/auth/sso/register/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { auth } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { REDACTED_MARKER } from '@/lib/core/security/redaction' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SSO-Register') diff --git a/apps/sim/app/api/auth/trello/authorize/route.ts b/apps/sim/app/api/auth/trello/authorize/route.ts index d3c05f9137..d5e23abf03 100644 --- a/apps/sim/app/api/auth/trello/authorize/route.ts +++ b/apps/sim/app/api/auth/trello/authorize/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TrelloAuthorize') diff --git a/apps/sim/app/api/auth/trello/store/route.ts b/apps/sim/app/api/auth/trello/store/route.ts index 081e03d6a5..fff52b0a84 100644 --- a/apps/sim/app/api/auth/trello/store/route.ts +++ b/apps/sim/app/api/auth/trello/store/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { safeAccountInsert } from '@/app/api/auth/oauth/utils' import { db } from '@/../../packages/db' import { account } from '@/../../packages/db/schema' diff --git a/apps/sim/app/api/billing/credits/route.ts b/apps/sim/app/api/billing/credits/route.ts index 31d9089f5e..9a87e8c928 100644 --- a/apps/sim/app/api/billing/credits/route.ts +++ b/apps/sim/app/api/billing/credits/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { getCreditBalance } from '@/lib/billing/credits/balance' import { purchaseCredits } from '@/lib/billing/credits/purchase' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CreditsAPI') diff --git a/apps/sim/app/api/billing/portal/route.ts b/apps/sim/app/api/billing/portal/route.ts index c68b24e669..eb1a860550 100644 --- a/apps/sim/app/api/billing/portal/route.ts +++ b/apps/sim/app/api/billing/portal/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { subscription as subscriptionTable, user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { requireStripeClient } from '@/lib/billing/stripe-client' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('BillingPortal') diff --git a/apps/sim/app/api/billing/route.ts b/apps/sim/app/api/billing/route.ts index 33e1559afa..5ccf2dfd4f 100644 --- a/apps/sim/app/api/billing/route.ts +++ b/apps/sim/app/api/billing/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { member, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { getSimplifiedBillingSummary } from '@/lib/billing/core/billing' import { getOrganizationBillingData } from '@/lib/billing/core/organization' -import { createLogger } from '@/lib/logs/console/logger' /** * Gets the effective billing blocked status for a user. @@ -93,6 +93,7 @@ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const context = searchParams.get('context') || 'user' const contextId = searchParams.get('id') + const includeOrg = searchParams.get('includeOrg') === 'true' // Validate context parameter if (!['user', 'organization'].includes(context)) { @@ -115,14 +116,38 @@ export async function GET(request: NextRequest) { if (context === 'user') { // Get user billing (may include organization if they're part of one) billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined) + // Attach effective billing blocked status (includes org owner check) const billingStatus = await getEffectiveBillingStatus(session.user.id) + billingData = { ...billingData, billingBlocked: billingStatus.billingBlocked, billingBlockedReason: billingStatus.billingBlockedReason, blockedByOrgOwner: billingStatus.blockedByOrgOwner, } + + // Optionally include organization membership and role + if (includeOrg) { + const userMembership = await db + .select({ + organizationId: member.organizationId, + role: member.role, + }) + .from(member) + .where(eq(member.userId, session.user.id)) + .limit(1) + + if (userMembership.length > 0) { + billingData = { + ...billingData, + organization: { + id: userMembership[0].organizationId, + role: userMembership[0].role as 'owner' | 'admin' | 'member', + }, + } + } + } } else { // Get user role in organization for permission checks first const memberRecord = await db diff --git a/apps/sim/app/api/billing/update-cost/route.ts b/apps/sim/app/api/billing/update-cost/route.ts index 4882f194d9..3e8e0a289d 100644 --- a/apps/sim/app/api/billing/update-cost/route.ts +++ b/apps/sim/app/api/billing/update-cost/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' import { checkInternalApiKey } from '@/lib/copilot/utils' import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('BillingUpdateCostAPI') diff --git a/apps/sim/app/api/careers/submit/route.ts b/apps/sim/app/api/careers/submit/route.ts index 5ed4b418ce..0d6e0646d7 100644 --- a/apps/sim/app/api/careers/submit/route.ts +++ b/apps/sim/app/api/careers/submit/route.ts @@ -1,10 +1,10 @@ import { render } from '@react-email/components' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import CareersConfirmationEmail from '@/components/emails/careers/careers-confirmation-email' import CareersSubmissionEmail from '@/components/emails/careers/careers-submission-email' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/chat/[identifier]/otp/route.test.ts b/apps/sim/app/api/chat/[identifier]/otp/route.test.ts new file mode 100644 index 0000000000..24526a80d6 --- /dev/null +++ b/apps/sim/app/api/chat/[identifier]/otp/route.test.ts @@ -0,0 +1,550 @@ +/** + * Tests for chat OTP API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +describe('Chat OTP API Route', () => { + const mockEmail = 'test@example.com' + const mockChatId = 'chat-123' + const mockIdentifier = 'test-chat' + const mockOTP = '123456' + + const mockRedisSet = vi.fn() + const mockRedisGet = vi.fn() + const mockRedisDel = vi.fn() + const mockGetRedisClient = vi.fn() + + const mockDbSelect = vi.fn() + const mockDbInsert = vi.fn() + const mockDbDelete = vi.fn() + + const mockSendEmail = vi.fn() + const mockRenderOTPEmail = vi.fn() + const mockAddCorsHeaders = vi.fn() + const mockCreateSuccessResponse = vi.fn() + const mockCreateErrorResponse = vi.fn() + const mockSetChatAuthCookie = vi.fn() + const mockGenerateRequestId = vi.fn() + + let storageMethod: 'redis' | 'database' = 'redis' + + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + + vi.spyOn(Math, 'random').mockReturnValue(0.123456) + vi.spyOn(Date, 'now').mockReturnValue(1640995200000) + + vi.stubGlobal('crypto', { + ...crypto, + randomUUID: vi.fn().mockReturnValue('test-uuid-1234'), + }) + + const mockRedisClient = { + set: mockRedisSet, + get: mockRedisGet, + del: mockRedisDel, + } + mockGetRedisClient.mockReturnValue(mockRedisClient) + mockRedisSet.mockResolvedValue('OK') + mockRedisGet.mockResolvedValue(null) + mockRedisDel.mockResolvedValue(1) + + vi.doMock('@/lib/core/config/redis', () => ({ + getRedisClient: mockGetRedisClient, + })) + + const createDbChain = (result: any) => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue(result), + }), + }), + }) + + mockDbSelect.mockImplementation(() => createDbChain([])) + mockDbInsert.mockImplementation(() => ({ + values: vi.fn().mockResolvedValue(undefined), + })) + mockDbDelete.mockImplementation(() => ({ + where: vi.fn().mockResolvedValue(undefined), + })) + + vi.doMock('@sim/db', () => ({ + db: { + select: mockDbSelect, + insert: mockDbInsert, + delete: mockDbDelete, + transaction: vi.fn(async (callback) => { + return callback({ + select: mockDbSelect, + insert: mockDbInsert, + delete: mockDbDelete, + }) + }), + }, + })) + + vi.doMock('@sim/db/schema', () => ({ + chat: { + id: 'id', + authType: 'authType', + allowedEmails: 'allowedEmails', + title: 'title', + }, + verification: { + id: 'id', + identifier: 'identifier', + value: 'value', + expiresAt: 'expiresAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + }, + })) + + vi.doMock('drizzle-orm', () => ({ + eq: vi.fn((field, value) => ({ field, value, type: 'eq' })), + and: vi.fn((...conditions) => ({ conditions, type: 'and' })), + gt: vi.fn((field, value) => ({ field, value, type: 'gt' })), + lt: vi.fn((field, value) => ({ field, value, type: 'lt' })), + })) + + vi.doMock('@/lib/core/storage', () => ({ + getStorageMethod: vi.fn(() => storageMethod), + })) + + mockSendEmail.mockResolvedValue({ success: true }) + mockRenderOTPEmail.mockResolvedValue('OTP Email') + + vi.doMock('@/lib/messaging/email/mailer', () => ({ + sendEmail: mockSendEmail, + })) + + vi.doMock('@/components/emails/render-email', () => ({ + renderOTPEmail: mockRenderOTPEmail, + })) + + mockAddCorsHeaders.mockImplementation((response) => response) + mockCreateSuccessResponse.mockImplementation((data) => ({ + json: () => Promise.resolve(data), + status: 200, + })) + mockCreateErrorResponse.mockImplementation((message, status) => ({ + json: () => Promise.resolve({ error: message }), + status, + })) + + vi.doMock('@/app/api/chat/utils', () => ({ + addCorsHeaders: mockAddCorsHeaders, + setChatAuthCookie: mockSetChatAuthCookie, + })) + + vi.doMock('@/app/api/workflows/utils', () => ({ + createSuccessResponse: mockCreateSuccessResponse, + createErrorResponse: mockCreateErrorResponse, + })) + + vi.doMock('@sim/logger', () => ({ + createLogger: vi.fn().mockReturnValue({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }), + })) + + vi.doMock('zod', () => ({ + z: { + object: vi.fn().mockReturnValue({ + parse: vi.fn().mockImplementation((data) => data), + }), + string: vi.fn().mockReturnValue({ + email: vi.fn().mockReturnThis(), + length: vi.fn().mockReturnThis(), + }), + }, + })) + + mockGenerateRequestId.mockReturnValue('req-123') + vi.doMock('@/lib/core/utils/request', () => ({ + generateRequestId: mockGenerateRequestId, + })) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('POST - Store OTP (Redis path)', () => { + beforeEach(() => { + storageMethod = 'redis' + }) + + it('should store OTP in Redis when storage method is redis', async () => { + const { POST } = await import('./route') + + mockDbSelect.mockImplementationOnce(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: mockChatId, + authType: 'email', + allowedEmails: [mockEmail], + title: 'Test Chat', + }, + ]), + }), + }), + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'POST', + body: JSON.stringify({ email: mockEmail }), + }) + + await POST(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockRedisSet).toHaveBeenCalledWith( + `otp:${mockEmail}:${mockChatId}`, + expect.any(String), + 'EX', + 900 // 15 minutes + ) + + expect(mockDbInsert).not.toHaveBeenCalled() + }) + }) + + describe('POST - Store OTP (Database path)', () => { + beforeEach(() => { + storageMethod = 'database' + mockGetRedisClient.mockReturnValue(null) + }) + + it('should store OTP in database when storage method is database', async () => { + const { POST } = await import('./route') + + mockDbSelect.mockImplementationOnce(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: mockChatId, + authType: 'email', + allowedEmails: [mockEmail], + title: 'Test Chat', + }, + ]), + }), + }), + })) + + const mockInsertValues = vi.fn().mockResolvedValue(undefined) + mockDbInsert.mockImplementationOnce(() => ({ + values: mockInsertValues, + })) + + const mockDeleteWhere = vi.fn().mockResolvedValue(undefined) + mockDbDelete.mockImplementation(() => ({ + where: mockDeleteWhere, + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'POST', + body: JSON.stringify({ email: mockEmail }), + }) + + await POST(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockDbDelete).toHaveBeenCalled() + + expect(mockDbInsert).toHaveBeenCalled() + expect(mockInsertValues).toHaveBeenCalledWith({ + id: expect.any(String), + identifier: `chat-otp:${mockChatId}:${mockEmail}`, + value: expect.any(String), + expiresAt: expect.any(Date), + createdAt: expect.any(Date), + updatedAt: expect.any(Date), + }) + + expect(mockRedisSet).not.toHaveBeenCalled() + }) + }) + + describe('PUT - Verify OTP (Redis path)', () => { + beforeEach(() => { + storageMethod = 'redis' + mockRedisGet.mockResolvedValue(mockOTP) + }) + + it('should retrieve OTP from Redis and verify successfully', async () => { + const { PUT } = await import('./route') + + mockDbSelect.mockImplementationOnce(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: mockChatId, + authType: 'email', + }, + ]), + }), + }), + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockRedisGet).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`) + + expect(mockRedisDel).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`) + + expect(mockDbSelect).toHaveBeenCalledTimes(1) + }) + }) + + describe('PUT - Verify OTP (Database path)', () => { + beforeEach(() => { + storageMethod = 'database' + mockGetRedisClient.mockReturnValue(null) + }) + + it('should retrieve OTP from database and verify successfully', async () => { + const { PUT } = await import('./route') + + let selectCallCount = 0 + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockImplementation(() => { + selectCallCount++ + if (selectCallCount === 1) { + return Promise.resolve([ + { + id: mockChatId, + authType: 'email', + }, + ]) + } + return Promise.resolve([ + { + value: mockOTP, + expiresAt: new Date(Date.now() + 10 * 60 * 1000), + }, + ]) + }), + }), + }), + })) + + const mockDeleteWhere = vi.fn().mockResolvedValue(undefined) + mockDbDelete.mockImplementation(() => ({ + where: mockDeleteWhere, + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockDbSelect).toHaveBeenCalledTimes(2) + + expect(mockDbDelete).toHaveBeenCalled() + + expect(mockRedisGet).not.toHaveBeenCalled() + }) + + it('should reject expired OTP from database', async () => { + const { PUT } = await import('./route') + + let selectCallCount = 0 + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockImplementation(() => { + selectCallCount++ + if (selectCallCount === 1) { + return Promise.resolve([ + { + id: mockChatId, + authType: 'email', + }, + ]) + } + return Promise.resolve([]) + }), + }), + }), + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockCreateErrorResponse).toHaveBeenCalledWith( + 'No verification code found, request a new one', + 400 + ) + }) + }) + + describe('DELETE OTP (Redis path)', () => { + beforeEach(() => { + storageMethod = 'redis' + }) + + it('should delete OTP from Redis after verification', async () => { + const { PUT } = await import('./route') + + mockRedisGet.mockResolvedValue(mockOTP) + + mockDbSelect.mockImplementationOnce(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: mockChatId, + authType: 'email', + }, + ]), + }), + }), + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockRedisDel).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`) + expect(mockDbDelete).not.toHaveBeenCalled() + }) + }) + + describe('DELETE OTP (Database path)', () => { + beforeEach(() => { + storageMethod = 'database' + mockGetRedisClient.mockReturnValue(null) + }) + + it('should delete OTP from database after verification', async () => { + const { PUT } = await import('./route') + + let selectCallCount = 0 + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockImplementation(() => { + selectCallCount++ + if (selectCallCount === 1) { + return Promise.resolve([{ id: mockChatId, authType: 'email' }]) + } + return Promise.resolve([ + { value: mockOTP, expiresAt: new Date(Date.now() + 10 * 60 * 1000) }, + ]) + }), + }), + }), + })) + + const mockDeleteWhere = vi.fn().mockResolvedValue(undefined) + mockDbDelete.mockImplementation(() => ({ + where: mockDeleteWhere, + })) + + const request = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockDbDelete).toHaveBeenCalled() + expect(mockRedisDel).not.toHaveBeenCalled() + }) + }) + + describe('Behavior consistency between Redis and Database', () => { + it('should have same behavior for missing OTP in both storage methods', async () => { + storageMethod = 'redis' + mockRedisGet.mockResolvedValue(null) + + const { PUT: PUTRedis } = await import('./route') + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([{ id: mockChatId, authType: 'email' }]), + }), + }), + })) + + const requestRedis = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'PUT', + body: JSON.stringify({ email: mockEmail, otp: mockOTP }), + }) + + await PUTRedis(requestRedis, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockCreateErrorResponse).toHaveBeenCalledWith( + 'No verification code found, request a new one', + 400 + ) + }) + + it('should have same OTP expiry time in both storage methods', async () => { + const OTP_EXPIRY = 15 * 60 + + storageMethod = 'redis' + const { POST: POSTRedis } = await import('./route') + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: mockChatId, + authType: 'email', + allowedEmails: [mockEmail], + title: 'Test Chat', + }, + ]), + }), + }), + })) + + const requestRedis = new NextRequest('http://localhost:3000/api/chat/test/otp', { + method: 'POST', + body: JSON.stringify({ email: mockEmail }), + }) + + await POSTRedis(requestRedis, { params: Promise.resolve({ identifier: mockIdentifier }) }) + + expect(mockRedisSet).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + 'EX', + OTP_EXPIRY + ) + }) + }) +}) diff --git a/apps/sim/app/api/chat/[identifier]/otp/route.ts b/apps/sim/app/api/chat/[identifier]/otp/route.ts index dd400b5905..52948e2bfc 100644 --- a/apps/sim/app/api/chat/[identifier]/otp/route.ts +++ b/apps/sim/app/api/chat/[identifier]/otp/route.ts @@ -1,13 +1,14 @@ +import { randomUUID } from 'crypto' import { db } from '@sim/db' -import { chat } from '@sim/db/schema' -import { eq } from 'drizzle-orm' +import { chat, verification } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { and, eq, gt } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' import { renderOTPEmail } from '@/components/emails/render-email' import { getRedisClient } from '@/lib/core/config/redis' import { getStorageMethod } from '@/lib/core/storage' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { addCorsHeaders, setChatAuthCookie } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -22,24 +23,11 @@ const OTP_EXPIRY = 15 * 60 // 15 minutes const OTP_EXPIRY_MS = OTP_EXPIRY * 1000 /** - * In-memory OTP storage for single-instance deployments without Redis. - * Only used when REDIS_URL is not configured (determined once at startup). - * - * Warning: This does NOT work in multi-instance/serverless deployments. + * Stores OTP in Redis or database depending on storage method. + * Uses the verification table for database storage. */ -const inMemoryOTPStore = new Map() - -function cleanupExpiredOTPs() { - const now = Date.now() - for (const [key, value] of inMemoryOTPStore.entries()) { - if (value.expiresAt < now) { - inMemoryOTPStore.delete(key) - } - } -} - async function storeOTP(email: string, chatId: string, otp: string): Promise { - const key = `otp:${email}:${chatId}` + const identifier = `chat-otp:${chatId}:${email}` const storageMethod = getStorageMethod() if (storageMethod === 'redis') { @@ -47,18 +35,28 @@ async function storeOTP(email: string, chatId: string, otp: string): Promise { + await tx.delete(verification).where(eq(verification.identifier, identifier)) + await tx.insert(verification).values({ + id: randomUUID(), + identifier, + value: otp, + expiresAt, + createdAt: now, + updatedAt: now, + }) }) } } async function getOTP(email: string, chatId: string): Promise { - const key = `otp:${email}:${chatId}` + const identifier = `chat-otp:${chatId}:${email}` const storageMethod = getStorageMethod() if (storageMethod === 'redis') { @@ -66,22 +64,27 @@ async function getOTP(email: string, chatId: string): Promise { if (!redis) { throw new Error('Redis configured but client unavailable') } + const key = `otp:${email}:${chatId}` return redis.get(key) } - const entry = inMemoryOTPStore.get(key) - if (!entry) return null + const now = new Date() + const [record] = await db + .select({ + value: verification.value, + expiresAt: verification.expiresAt, + }) + .from(verification) + .where(and(eq(verification.identifier, identifier), gt(verification.expiresAt, now))) + .limit(1) - if (entry.expiresAt < Date.now()) { - inMemoryOTPStore.delete(key) - return null - } + if (!record) return null - return entry.otp + return record.value } async function deleteOTP(email: string, chatId: string): Promise { - const key = `otp:${email}:${chatId}` + const identifier = `chat-otp:${chatId}:${email}` const storageMethod = getStorageMethod() if (storageMethod === 'redis') { @@ -89,9 +92,10 @@ async function deleteOTP(email: string, chatId: string): Promise { if (!redis) { throw new Error('Redis configured but client unavailable') } + const key = `otp:${email}:${chatId}` await redis.del(key) } else { - inMemoryOTPStore.delete(key) + await db.delete(verification).where(eq(verification.identifier, identifier)) } } diff --git a/apps/sim/app/api/chat/[identifier]/route.test.ts b/apps/sim/app/api/chat/[identifier]/route.test.ts index f23f6cad4e..b63aa797f6 100644 --- a/apps/sim/app/api/chat/[identifier]/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/route.test.ts @@ -120,7 +120,7 @@ describe('Chat Identifier API Route', () => { validateAuthToken: vi.fn().mockReturnValue(true), })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue({ debug: vi.fn(), info: vi.fn(), diff --git a/apps/sim/app/api/chat/[identifier]/route.ts b/apps/sim/app/api/chat/[identifier]/route.ts index ad3cb48746..5754d38b24 100644 --- a/apps/sim/app/api/chat/[identifier]/route.ts +++ b/apps/sim/app/api/chat/[identifier]/route.ts @@ -1,12 +1,12 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { chat, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' import { preprocessExecution } from '@/lib/execution/preprocessing' -import { createLogger } from '@/lib/logs/console/logger' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { ChatFiles } from '@/lib/uploads' import { diff --git a/apps/sim/app/api/chat/manage/[id]/route.test.ts b/apps/sim/app/api/chat/manage/[id]/route.test.ts index 6e1d445ba9..1be5f483b2 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.test.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.test.ts @@ -50,7 +50,7 @@ describe('Chat Edit API Route', () => { chat: { id: 'id', identifier: 'identifier', userId: 'userId' }, })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue({ info: vi.fn(), error: vi.fn(), diff --git a/apps/sim/app/api/chat/manage/[id]/route.ts b/apps/sim/app/api/chat/manage/[id]/route.ts index d7141aa2e0..236ae10015 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' @@ -7,7 +8,6 @@ import { getSession } from '@/lib/auth' import { isDev } from '@/lib/core/config/feature-flags' import { encryptSecret } from '@/lib/core/security/encryption' import { getEmailDomain } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { deployWorkflow } from '@/lib/workflows/persistence/utils' import { checkChatAccess } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/chat/route.test.ts b/apps/sim/app/api/chat/route.test.ts index 7156c80c72..0eb6288834 100644 --- a/apps/sim/app/api/chat/route.test.ts +++ b/apps/sim/app/api/chat/route.test.ts @@ -42,7 +42,7 @@ describe('Chat API Route', () => { workflow: { id: 'id', userId: 'userId', isDeployed: 'isDeployed' }, })) - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue({ info: vi.fn(), error: vi.fn(), diff --git a/apps/sim/app/api/chat/route.ts b/apps/sim/app/api/chat/route.ts index 3a49f32cf0..dd736b6529 100644 --- a/apps/sim/app/api/chat/route.ts +++ b/apps/sim/app/api/chat/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { v4 as uuidv4 } from 'uuid' @@ -8,7 +9,6 @@ import { getSession } from '@/lib/auth' import { isDev } from '@/lib/core/config/feature-flags' import { encryptSecret } from '@/lib/core/security/encryption' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { deployWorkflow } from '@/lib/workflows/persistence/utils' import { checkWorkflowAccessForChatCreation } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index 188c03b110..70d92990b4 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -52,7 +52,7 @@ vi.mock('@/lib/core/config/feature-flags', () => ({ describe('Chat API Utils', () => { beforeEach(() => { - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue({ info: vi.fn(), error: vi.fn(), diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index 94cc1ec300..712886a2ff 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -1,11 +1,11 @@ import { createHash } from 'crypto' import { db } from '@sim/db' import { chat, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest, NextResponse } from 'next/server' import { isDev } from '@/lib/core/config/feature-flags' import { decryptSecret } from '@/lib/core/security/encryption' -import { createLogger } from '@/lib/logs/console/logger' import { hasAdminPermission } from '@/lib/workspaces/permissions/utils' const logger = createLogger('ChatAuthUtils') diff --git a/apps/sim/app/api/chat/validate/route.ts b/apps/sim/app/api/chat/validate/route.ts index 0cfd91f7f5..0aecbd66f0 100644 --- a/apps/sim/app/api/chat/validate/route.ts +++ b/apps/sim/app/api/chat/validate/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' const logger = createLogger('ChatValidateAPI') diff --git a/apps/sim/app/api/copilot/api-keys/route.test.ts b/apps/sim/app/api/copilot/api-keys/route.test.ts new file mode 100644 index 0000000000..2556a7e93a --- /dev/null +++ b/apps/sim/app/api/copilot/api-keys/route.test.ts @@ -0,0 +1,361 @@ +/** + * Tests for copilot api-keys API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mockAuth, mockCryptoUuid, setupCommonApiMocks } from '@/app/api/__test-utils__/utils' + +describe('Copilot API Keys API Route', () => { + const mockFetch = vi.fn() + + beforeEach(() => { + vi.resetModules() + setupCommonApiMocks() + mockCryptoUuid() + + global.fetch = mockFetch + + vi.doMock('@/lib/copilot/constants', () => ({ + SIM_AGENT_API_URL_DEFAULT: 'https://agent.sim.example.com', + })) + + vi.doMock('@/lib/core/config/env', () => ({ + env: { + SIM_AGENT_API_URL: null, + COPILOT_API_KEY: 'test-api-key', + }, + })) + }) + + afterEach(() => { + vi.clearAllMocks() + vi.restoreAllMocks() + }) + + describe('GET', () => { + it('should return 401 when user is not authenticated', async () => { + const authMocks = mockAuth() + authMocks.setUnauthenticated() + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should return list of API keys with masked values', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const mockApiKeys = [ + { + id: 'key-1', + apiKey: 'sk-sim-abcdefghijklmnopqrstuv', + name: 'Production Key', + createdAt: '2024-01-01T00:00:00.000Z', + lastUsed: '2024-01-15T00:00:00.000Z', + }, + { + id: 'key-2', + apiKey: 'sk-sim-zyxwvutsrqponmlkjihgfe', + name: null, + createdAt: '2024-01-02T00:00:00.000Z', + lastUsed: null, + }, + ] + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve(mockApiKeys), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.keys).toHaveLength(2) + expect(responseData.keys[0].id).toBe('key-1') + expect(responseData.keys[0].displayKey).toBe('•••••qrstuv') + expect(responseData.keys[0].name).toBe('Production Key') + expect(responseData.keys[1].displayKey).toBe('•••••jihgfe') + expect(responseData.keys[1].name).toBeNull() + }) + + it('should return empty array when user has no API keys', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve([]), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.keys).toEqual([]) + }) + + it('should forward userId to Sim Agent', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve([]), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + await GET(request) + + expect(mockFetch).toHaveBeenCalledWith( + 'https://agent.sim.example.com/api/validate-key/get-api-keys', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'x-api-key': 'test-api-key', + }), + body: JSON.stringify({ userId: 'user-123' }), + }) + ) + }) + + it('should return error when Sim Agent returns non-ok response', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 503, + json: () => Promise.resolve({ error: 'Service unavailable' }), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(503) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Failed to get keys' }) + }) + + it('should return 500 when Sim Agent returns invalid response', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ invalid: 'response' }), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Invalid response from Sim Agent' }) + }) + + it('should handle network errors gracefully', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockRejectedValueOnce(new Error('Network error')) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Failed to get keys' }) + }) + + it('should handle API keys with empty apiKey string', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const mockApiKeys = [ + { + id: 'key-1', + apiKey: '', + name: 'Empty Key', + createdAt: '2024-01-01T00:00:00.000Z', + lastUsed: null, + }, + ] + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve(mockApiKeys), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.keys[0].displayKey).toBe('•••••') + }) + + it('should handle JSON parsing errors from Sim Agent', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.reject(new Error('Invalid JSON')), + }) + + const { GET } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await GET(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Invalid response from Sim Agent' }) + }) + }) + + describe('DELETE', () => { + it('should return 401 when user is not authenticated', async () => { + const authMocks = mockAuth() + authMocks.setUnauthenticated() + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') + const response = await DELETE(request) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should return 400 when id parameter is missing', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys') + const response = await DELETE(request) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'id is required' }) + }) + + it('should successfully delete an API key', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') + const response = await DELETE(request) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData).toEqual({ success: true }) + + expect(mockFetch).toHaveBeenCalledWith( + 'https://agent.sim.example.com/api/validate-key/delete', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'x-api-key': 'test-api-key', + }), + body: JSON.stringify({ userId: 'user-123', apiKeyId: 'key-123' }), + }) + ) + }) + + it('should return error when Sim Agent returns non-ok response', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 404, + json: () => Promise.resolve({ error: 'Key not found' }), + }) + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=non-existent') + const response = await DELETE(request) + + expect(response.status).toBe(404) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Failed to delete key' }) + }) + + it('should return 500 when Sim Agent returns invalid response', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ success: false }), + }) + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') + const response = await DELETE(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Invalid response from Sim Agent' }) + }) + + it('should handle network errors gracefully', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockRejectedValueOnce(new Error('Network error')) + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') + const response = await DELETE(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Failed to delete key' }) + }) + + it('should handle JSON parsing errors from Sim Agent on delete', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.reject(new Error('Invalid JSON')), + }) + + const { DELETE } = await import('@/app/api/copilot/api-keys/route') + const request = new NextRequest('http://localhost:3000/api/copilot/api-keys?id=key-123') + const response = await DELETE(request) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Invalid response from Sim Agent' }) + }) + }) +}) diff --git a/apps/sim/app/api/copilot/api-keys/validate/route.ts b/apps/sim/app/api/copilot/api-keys/validate/route.ts index b0204aef8a..77521f3b3e 100644 --- a/apps/sim/app/api/copilot/api-keys/validate/route.ts +++ b/apps/sim/app/api/copilot/api-keys/validate/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkServerSideUsageLimits } from '@/lib/billing/calculations/usage-monitor' import { checkInternalApiKey } from '@/lib/copilot/utils' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotApiKeysValidate') diff --git a/apps/sim/app/api/copilot/auto-allowed-tools/route.ts b/apps/sim/app/api/copilot/auto-allowed-tools/route.ts index 13a2d2e9e7..ecf6aa7f76 100644 --- a/apps/sim/app/api/copilot/auto-allowed-tools/route.ts +++ b/apps/sim/app/api/copilot/auto-allowed-tools/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { settings } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotAutoAllowedToolsAPI') diff --git a/apps/sim/app/api/copilot/chat/delete/route.test.ts b/apps/sim/app/api/copilot/chat/delete/route.test.ts new file mode 100644 index 0000000000..af36cfb5e0 --- /dev/null +++ b/apps/sim/app/api/copilot/chat/delete/route.test.ts @@ -0,0 +1,189 @@ +/** + * Tests for copilot chat delete API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMockRequest, + mockAuth, + mockCryptoUuid, + setupCommonApiMocks, +} from '@/app/api/__test-utils__/utils' + +describe('Copilot Chat Delete API Route', () => { + const mockDelete = vi.fn() + const mockWhere = vi.fn() + + beforeEach(() => { + vi.resetModules() + setupCommonApiMocks() + mockCryptoUuid() + + mockDelete.mockReturnValue({ where: mockWhere }) + mockWhere.mockResolvedValue([]) + + vi.doMock('@sim/db', () => ({ + db: { + delete: mockDelete, + }, + })) + + vi.doMock('@sim/db/schema', () => ({ + copilotChats: { + id: 'id', + userId: 'userId', + }, + })) + + vi.doMock('drizzle-orm', () => ({ + eq: vi.fn((field, value) => ({ field, value, type: 'eq' })), + })) + }) + + afterEach(() => { + vi.clearAllMocks() + vi.restoreAllMocks() + }) + + describe('DELETE', () => { + it('should return 401 when user is not authenticated', async () => { + const authMocks = mockAuth() + authMocks.setUnauthenticated() + + const req = createMockRequest('DELETE', { + chatId: 'chat-123', + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ success: false, error: 'Unauthorized' }) + }) + + it('should successfully delete a chat', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockWhere.mockResolvedValueOnce([{ id: 'chat-123' }]) + + const req = createMockRequest('DELETE', { + chatId: 'chat-123', + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData).toEqual({ success: true }) + + expect(mockDelete).toHaveBeenCalled() + expect(mockWhere).toHaveBeenCalled() + }) + + it('should return 500 for invalid request body - missing chatId', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const req = createMockRequest('DELETE', {}) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to delete chat') + }) + + it('should return 500 for invalid request body - chatId is not a string', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const req = createMockRequest('DELETE', { + chatId: 12345, + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to delete chat') + }) + + it('should handle database errors gracefully', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockWhere.mockRejectedValueOnce(new Error('Database connection failed')) + + const req = createMockRequest('DELETE', { + chatId: 'chat-123', + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData).toEqual({ success: false, error: 'Failed to delete chat' }) + }) + + it('should handle JSON parsing errors in request body', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const req = new NextRequest('http://localhost:3000/api/copilot/chat/delete', { + method: 'DELETE', + body: '{invalid-json', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to delete chat') + }) + + it('should delete chat even if it does not exist (idempotent)', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + mockWhere.mockResolvedValueOnce([]) + + const req = createMockRequest('DELETE', { + chatId: 'non-existent-chat', + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData).toEqual({ success: true }) + }) + + it('should delete chat with empty string chatId (validation should fail)', async () => { + const authMocks = mockAuth() + authMocks.setAuthenticated() + + const req = createMockRequest('DELETE', { + chatId: '', + }) + + const { DELETE } = await import('@/app/api/copilot/chat/delete/route') + const response = await DELETE(req) + + expect(response.status).toBe(200) + expect(mockDelete).toHaveBeenCalled() + }) + }) +}) diff --git a/apps/sim/app/api/copilot/chat/delete/route.ts b/apps/sim/app/api/copilot/chat/delete/route.ts index 203a2b5c6d..8e900217b4 100644 --- a/apps/sim/app/api/copilot/chat/delete/route.ts +++ b/apps/sim/app/api/copilot/chat/delete/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('DeleteChatAPI') diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index eb7331e0ea..b14feb495d 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -17,7 +18,6 @@ import { import { getCredentialsServerTool } from '@/lib/copilot/tools/server/user/get-credentials' import type { CopilotProviderConfig } from '@/lib/copilot/types' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { CopilotFiles } from '@/lib/uploads' import { createFileContent } from '@/lib/uploads/utils/file-utils' import { tools } from '@/tools/registry' @@ -1066,7 +1066,6 @@ export async function GET(req: NextRequest) { model: chat.model, messages: Array.isArray(chat.messages) ? chat.messages : [], messageCount: Array.isArray(chat.messages) ? chat.messages.length : 0, - previewYaml: null, // Not needed for chat list planArtifact: chat.planArtifact || null, config: chat.config || null, createdAt: chat.createdAt, diff --git a/apps/sim/app/api/copilot/chat/update-messages/route.ts b/apps/sim/app/api/copilot/chat/update-messages/route.ts index bc17ddad52..4e25d726d9 100644 --- a/apps/sim/app/api/copilot/chat/update-messages/route.ts +++ b/apps/sim/app/api/copilot/chat/update-messages/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -10,7 +11,6 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotChatUpdateAPI') diff --git a/apps/sim/app/api/copilot/chat/update-title/route.ts b/apps/sim/app/api/copilot/chat/update-title/route.ts index c4266b7579..7c1451c642 100644 --- a/apps/sim/app/api/copilot/chat/update-title/route.ts +++ b/apps/sim/app/api/copilot/chat/update-title/route.ts @@ -5,11 +5,11 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UpdateChatTitleAPI') diff --git a/apps/sim/app/api/copilot/chats/route.test.ts b/apps/sim/app/api/copilot/chats/route.test.ts new file mode 100644 index 0000000000..8cc3bb04e5 --- /dev/null +++ b/apps/sim/app/api/copilot/chats/route.test.ts @@ -0,0 +1,277 @@ +/** + * Tests for copilot chats list API route + * + * @vitest-environment node + */ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { mockCryptoUuid, setupCommonApiMocks } from '@/app/api/__test-utils__/utils' + +describe('Copilot Chats List API Route', () => { + const mockSelect = vi.fn() + const mockFrom = vi.fn() + const mockWhere = vi.fn() + const mockOrderBy = vi.fn() + + beforeEach(() => { + vi.resetModules() + setupCommonApiMocks() + mockCryptoUuid() + + mockSelect.mockReturnValue({ from: mockFrom }) + mockFrom.mockReturnValue({ where: mockWhere }) + mockWhere.mockReturnValue({ orderBy: mockOrderBy }) + mockOrderBy.mockResolvedValue([]) + + vi.doMock('@sim/db', () => ({ + db: { + select: mockSelect, + }, + })) + + vi.doMock('@sim/db/schema', () => ({ + copilotChats: { + id: 'id', + title: 'title', + workflowId: 'workflowId', + userId: 'userId', + updatedAt: 'updatedAt', + }, + })) + + vi.doMock('drizzle-orm', () => ({ + and: vi.fn((...conditions) => ({ conditions, type: 'and' })), + eq: vi.fn((field, value) => ({ field, value, type: 'eq' })), + desc: vi.fn((field) => ({ field, type: 'desc' })), + })) + + vi.doMock('@/lib/copilot/request-helpers', () => ({ + authenticateCopilotRequestSessionOnly: vi.fn(), + createUnauthorizedResponse: vi + .fn() + .mockReturnValue(new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })), + createInternalServerErrorResponse: vi + .fn() + .mockImplementation( + (message) => new Response(JSON.stringify({ error: message }), { status: 500 }) + ), + })) + }) + + afterEach(() => { + vi.clearAllMocks() + vi.restoreAllMocks() + }) + + describe('GET', () => { + it('should return 401 when user is not authenticated', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: null, + isAuthenticated: false, + }) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should return empty chats array when user has no chats', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockOrderBy.mockResolvedValueOnce([]) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData).toEqual({ + success: true, + chats: [], + }) + }) + + it('should return list of chats for authenticated user', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const mockChats = [ + { + id: 'chat-1', + title: 'First Chat', + workflowId: 'workflow-1', + updatedAt: new Date('2024-01-02'), + }, + { + id: 'chat-2', + title: 'Second Chat', + workflowId: 'workflow-2', + updatedAt: new Date('2024-01-01'), + }, + ] + mockOrderBy.mockResolvedValueOnce(mockChats) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + expect(responseData.chats).toHaveLength(2) + expect(responseData.chats[0].id).toBe('chat-1') + expect(responseData.chats[0].title).toBe('First Chat') + expect(responseData.chats[1].id).toBe('chat-2') + }) + + it('should return chats ordered by updatedAt descending', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const mockChats = [ + { + id: 'newest-chat', + title: 'Newest', + workflowId: 'workflow-1', + updatedAt: new Date('2024-01-10'), + }, + { + id: 'older-chat', + title: 'Older', + workflowId: 'workflow-2', + updatedAt: new Date('2024-01-05'), + }, + { + id: 'oldest-chat', + title: 'Oldest', + workflowId: 'workflow-3', + updatedAt: new Date('2024-01-01'), + }, + ] + mockOrderBy.mockResolvedValueOnce(mockChats) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.chats[0].id).toBe('newest-chat') + expect(responseData.chats[2].id).toBe('oldest-chat') + }) + + it('should handle chats with null workflowId', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const mockChats = [ + { + id: 'chat-no-workflow', + title: 'Chat without workflow', + workflowId: null, + updatedAt: new Date('2024-01-01'), + }, + ] + mockOrderBy.mockResolvedValueOnce(mockChats) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.chats[0].workflowId).toBeNull() + }) + + it('should handle database errors gracefully', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockOrderBy.mockRejectedValueOnce(new Error('Database connection failed')) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to fetch user chats') + }) + + it('should only return chats belonging to authenticated user', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const mockChats = [ + { + id: 'my-chat', + title: 'My Chat', + workflowId: 'workflow-1', + updatedAt: new Date('2024-01-01'), + }, + ] + mockOrderBy.mockResolvedValueOnce(mockChats) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + await GET(request as any) + + expect(mockSelect).toHaveBeenCalled() + expect(mockWhere).toHaveBeenCalled() + }) + + it('should return 401 when userId is null despite isAuthenticated being true', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: null, + isAuthenticated: true, + }) + + const { GET } = await import('@/app/api/copilot/chats/route') + const request = new Request('http://localhost:3000/api/copilot/chats') + const response = await GET(request as any) + + expect(response.status).toBe(401) + }) + }) +}) diff --git a/apps/sim/app/api/copilot/chats/route.ts b/apps/sim/app/api/copilot/chats/route.ts index 03dcd6dccd..e7b82e2d63 100644 --- a/apps/sim/app/api/copilot/chats/route.ts +++ b/apps/sim/app/api/copilot/chats/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { copilotChats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -7,7 +8,6 @@ import { createInternalServerErrorResponse, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotChatsListAPI') diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.ts index f0b635f20e..7f65e0317e 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { workflowCheckpoints, workflow as workflowTable } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -11,7 +12,6 @@ import { createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { isUuidV4 } from '@/executor/constants' const logger = createLogger('CheckpointRevertAPI') diff --git a/apps/sim/app/api/copilot/checkpoints/route.ts b/apps/sim/app/api/copilot/checkpoints/route.ts index 5110ae12f9..b1517986a0 100644 --- a/apps/sim/app/api/copilot/checkpoints/route.ts +++ b/apps/sim/app/api/copilot/checkpoints/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { copilotChats, workflowCheckpoints } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -10,7 +11,6 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('WorkflowCheckpointsAPI') diff --git a/apps/sim/app/api/copilot/confirm/route.ts b/apps/sim/app/api/copilot/confirm/route.ts index fed0ad3dff..9fd5476c9e 100644 --- a/apps/sim/app/api/copilot/confirm/route.ts +++ b/apps/sim/app/api/copilot/confirm/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { @@ -9,7 +10,6 @@ import { type NotificationStatus, } from '@/lib/copilot/request-helpers' import { getRedisClient } from '@/lib/core/config/redis' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotConfirmAPI') diff --git a/apps/sim/app/api/copilot/context-usage/route.ts b/apps/sim/app/api/copilot/context-usage/route.ts index fba208bb44..ac8f834327 100644 --- a/apps/sim/app/api/copilot/context-usage/route.ts +++ b/apps/sim/app/api/copilot/context-usage/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -5,7 +6,6 @@ import { getCopilotModel } from '@/lib/copilot/config' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants' import type { CopilotProviderConfig } from '@/lib/copilot/types' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ContextUsageAPI') diff --git a/apps/sim/app/api/copilot/execute-copilot-server-tool/route.ts b/apps/sim/app/api/copilot/execute-copilot-server-tool/route.ts index c4bdbf4d8c..5627ae8976 100644 --- a/apps/sim/app/api/copilot/execute-copilot-server-tool/route.ts +++ b/apps/sim/app/api/copilot/execute-copilot-server-tool/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { @@ -8,7 +9,6 @@ import { createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' import { routeExecution } from '@/lib/copilot/tools/server/router' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ExecuteCopilotServerToolAPI') diff --git a/apps/sim/app/api/copilot/execute-tool/route.ts b/apps/sim/app/api/copilot/execute-tool/route.ts index e5cb66095f..adb88071e3 100644 --- a/apps/sim/app/api/copilot/execute-tool/route.ts +++ b/apps/sim/app/api/copilot/execute-tool/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { account, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,7 +13,6 @@ import { } from '@/lib/copilot/request-helpers' import { generateRequestId } from '@/lib/core/utils/request' import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' -import { createLogger } from '@/lib/logs/console/logger' import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { REFERENCE } from '@/executor/constants' import { createEnvVarPattern } from '@/executor/utils/reference-validation' diff --git a/apps/sim/app/api/copilot/feedback/route.test.ts b/apps/sim/app/api/copilot/feedback/route.test.ts new file mode 100644 index 0000000000..547d5cd3b9 --- /dev/null +++ b/apps/sim/app/api/copilot/feedback/route.test.ts @@ -0,0 +1,516 @@ +/** + * Tests for copilot feedback API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMockRequest, + mockCryptoUuid, + setupCommonApiMocks, +} from '@/app/api/__test-utils__/utils' + +describe('Copilot Feedback API Route', () => { + const mockInsert = vi.fn() + const mockValues = vi.fn() + const mockReturning = vi.fn() + const mockSelect = vi.fn() + const mockFrom = vi.fn() + + beforeEach(() => { + vi.resetModules() + setupCommonApiMocks() + mockCryptoUuid() + + mockInsert.mockReturnValue({ values: mockValues }) + mockValues.mockReturnValue({ returning: mockReturning }) + mockReturning.mockResolvedValue([]) + mockSelect.mockReturnValue({ from: mockFrom }) + mockFrom.mockResolvedValue([]) + + vi.doMock('@sim/db', () => ({ + db: { + insert: mockInsert, + select: mockSelect, + }, + })) + + vi.doMock('@sim/db/schema', () => ({ + copilotFeedback: { + feedbackId: 'feedbackId', + userId: 'userId', + chatId: 'chatId', + userQuery: 'userQuery', + agentResponse: 'agentResponse', + isPositive: 'isPositive', + feedback: 'feedback', + workflowYaml: 'workflowYaml', + createdAt: 'createdAt', + }, + })) + + vi.doMock('drizzle-orm', () => ({ + eq: vi.fn((field, value) => ({ field, value, type: 'eq' })), + })) + + vi.doMock('@/lib/copilot/request-helpers', () => ({ + authenticateCopilotRequestSessionOnly: vi.fn(), + createUnauthorizedResponse: vi + .fn() + .mockReturnValue(new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })), + createBadRequestResponse: vi + .fn() + .mockImplementation( + (message) => new Response(JSON.stringify({ error: message }), { status: 400 }) + ), + createInternalServerErrorResponse: vi + .fn() + .mockImplementation( + (message) => new Response(JSON.stringify({ error: message }), { status: 500 }) + ), + createRequestTracker: vi.fn().mockReturnValue({ + requestId: 'test-request-id', + getDuration: vi.fn().mockReturnValue(100), + }), + })) + }) + + afterEach(() => { + vi.clearAllMocks() + vi.restoreAllMocks() + }) + + describe('POST', () => { + it('should return 401 when user is not authenticated', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: null, + isAuthenticated: false, + }) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should successfully submit positive feedback', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const feedbackRecord = { + feedbackId: 'feedback-123', + userId: 'user-123', + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + isPositive: true, + feedback: null, + workflowYaml: null, + createdAt: new Date('2024-01-01'), + } + mockReturning.mockResolvedValueOnce([feedbackRecord]) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + expect(responseData.feedbackId).toBe('feedback-123') + expect(responseData.message).toBe('Feedback submitted successfully') + }) + + it('should successfully submit negative feedback with text', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const feedbackRecord = { + feedbackId: 'feedback-456', + userId: 'user-123', + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I deploy?', + agentResponse: 'Here is how to deploy...', + isPositive: false, + feedback: 'The response was not helpful', + workflowYaml: null, + createdAt: new Date('2024-01-01'), + } + mockReturning.mockResolvedValueOnce([feedbackRecord]) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I deploy?', + agentResponse: 'Here is how to deploy...', + isPositiveFeedback: false, + feedback: 'The response was not helpful', + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + expect(responseData.feedbackId).toBe('feedback-456') + }) + + it('should successfully submit feedback with workflow YAML', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const workflowYaml = ` +blocks: + - id: starter + type: starter + - id: agent + type: agent +edges: + - source: starter + target: agent +` + + const feedbackRecord = { + feedbackId: 'feedback-789', + userId: 'user-123', + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'Build a simple agent workflow', + agentResponse: 'I created a workflow for you.', + isPositive: true, + feedback: null, + workflowYaml: workflowYaml, + createdAt: new Date('2024-01-01'), + } + mockReturning.mockResolvedValueOnce([feedbackRecord]) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'Build a simple agent workflow', + agentResponse: 'I created a workflow for you.', + isPositiveFeedback: true, + workflowYaml: workflowYaml, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + + expect(mockValues).toHaveBeenCalledWith( + expect.objectContaining({ + workflowYaml: workflowYaml, + }) + ) + }) + + it('should return 400 for invalid chatId format', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + chatId: 'not-a-uuid', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toContain('Invalid request data') + }) + + it('should return 400 for empty userQuery', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: '', + agentResponse: 'You can create a workflow by...', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toContain('Invalid request data') + }) + + it('should return 400 for empty agentResponse', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: '', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toContain('Invalid request data') + }) + + it('should return 400 for missing isPositiveFeedback', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toContain('Invalid request data') + }) + + it('should handle database errors gracefully', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockReturning.mockRejectedValueOnce(new Error('Database connection failed')) + + const req = createMockRequest('POST', { + chatId: '550e8400-e29b-41d4-a716-446655440000', + userQuery: 'How do I create a workflow?', + agentResponse: 'You can create a workflow by...', + isPositiveFeedback: true, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to submit feedback') + }) + + it('should handle JSON parsing errors in request body', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = new NextRequest('http://localhost:3000/api/copilot/feedback', { + method: 'POST', + body: '{invalid-json', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const { POST } = await import('@/app/api/copilot/feedback/route') + const response = await POST(req) + + expect(response.status).toBe(500) + }) + }) + + describe('GET', () => { + it('should return 401 when user is not authenticated', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: null, + isAuthenticated: false, + }) + + const { GET } = await import('@/app/api/copilot/feedback/route') + const request = new Request('http://localhost:3000/api/copilot/feedback') + const response = await GET(request as any) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should return empty feedback array when no feedback exists', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFrom.mockResolvedValueOnce([]) + + const { GET } = await import('@/app/api/copilot/feedback/route') + const request = new Request('http://localhost:3000/api/copilot/feedback') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + expect(responseData.feedback).toEqual([]) + }) + + it('should return all feedback records', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const mockFeedback = [ + { + feedbackId: 'feedback-1', + userId: 'user-123', + chatId: 'chat-1', + userQuery: 'Query 1', + agentResponse: 'Response 1', + isPositive: true, + feedback: null, + workflowYaml: null, + createdAt: new Date('2024-01-01'), + }, + { + feedbackId: 'feedback-2', + userId: 'user-456', + chatId: 'chat-2', + userQuery: 'Query 2', + agentResponse: 'Response 2', + isPositive: false, + feedback: 'Not helpful', + workflowYaml: 'yaml: content', + createdAt: new Date('2024-01-02'), + }, + ] + mockFrom.mockResolvedValueOnce(mockFeedback) + + const { GET } = await import('@/app/api/copilot/feedback/route') + const request = new Request('http://localhost:3000/api/copilot/feedback') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.success).toBe(true) + expect(responseData.feedback).toHaveLength(2) + expect(responseData.feedback[0].feedbackId).toBe('feedback-1') + expect(responseData.feedback[1].feedbackId).toBe('feedback-2') + }) + + it('should handle database errors gracefully', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFrom.mockRejectedValueOnce(new Error('Database connection failed')) + + const { GET } = await import('@/app/api/copilot/feedback/route') + const request = new Request('http://localhost:3000/api/copilot/feedback') + const response = await GET(request as any) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to retrieve feedback') + }) + + it('should return metadata with response', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFrom.mockResolvedValueOnce([]) + + const { GET } = await import('@/app/api/copilot/feedback/route') + const request = new Request('http://localhost:3000/api/copilot/feedback') + const response = await GET(request as any) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData.metadata).toBeDefined() + expect(responseData.metadata.requestId).toBeDefined() + expect(responseData.metadata.duration).toBeDefined() + }) + }) +}) diff --git a/apps/sim/app/api/copilot/feedback/route.ts b/apps/sim/app/api/copilot/feedback/route.ts index 86a95a9fc0..3ff0956122 100644 --- a/apps/sim/app/api/copilot/feedback/route.ts +++ b/apps/sim/app/api/copilot/feedback/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { copilotFeedback } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { @@ -9,7 +10,6 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotFeedbackAPI') diff --git a/apps/sim/app/api/copilot/stats/route.test.ts b/apps/sim/app/api/copilot/stats/route.test.ts new file mode 100644 index 0000000000..e48dff0169 --- /dev/null +++ b/apps/sim/app/api/copilot/stats/route.test.ts @@ -0,0 +1,367 @@ +/** + * Tests for copilot stats API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createMockRequest, + mockCryptoUuid, + setupCommonApiMocks, +} from '@/app/api/__test-utils__/utils' + +describe('Copilot Stats API Route', () => { + const mockFetch = vi.fn() + + beforeEach(() => { + vi.resetModules() + setupCommonApiMocks() + mockCryptoUuid() + + global.fetch = mockFetch + + vi.doMock('@/lib/copilot/request-helpers', () => ({ + authenticateCopilotRequestSessionOnly: vi.fn(), + createUnauthorizedResponse: vi + .fn() + .mockReturnValue(new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })), + createBadRequestResponse: vi + .fn() + .mockImplementation( + (message) => new Response(JSON.stringify({ error: message }), { status: 400 }) + ), + createInternalServerErrorResponse: vi + .fn() + .mockImplementation( + (message) => new Response(JSON.stringify({ error: message }), { status: 500 }) + ), + createRequestTracker: vi.fn().mockReturnValue({ + requestId: 'test-request-id', + getDuration: vi.fn().mockReturnValue(100), + }), + })) + + vi.doMock('@/lib/copilot/constants', () => ({ + SIM_AGENT_API_URL_DEFAULT: 'https://agent.sim.example.com', + })) + + vi.doMock('@/lib/core/config/env', () => ({ + env: { + SIM_AGENT_API_URL: null, + COPILOT_API_KEY: 'test-api-key', + }, + })) + }) + + afterEach(() => { + vi.clearAllMocks() + vi.restoreAllMocks() + }) + + describe('POST', () => { + it('should return 401 when user is not authenticated', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: null, + isAuthenticated: false, + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(401) + const responseData = await response.json() + expect(responseData).toEqual({ error: 'Unauthorized' }) + }) + + it('should successfully forward stats to Sim Agent', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + diffAccepted: true, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(200) + const responseData = await response.json() + expect(responseData).toEqual({ success: true }) + + expect(mockFetch).toHaveBeenCalledWith( + 'https://agent.sim.example.com/api/stats', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'x-api-key': 'test-api-key', + }), + body: JSON.stringify({ + messageId: 'message-123', + diffCreated: true, + diffAccepted: true, + }), + }) + ) + }) + + it('should return 400 for invalid request body - missing messageId', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toBe('Invalid request body for copilot stats') + }) + + it('should return 400 for invalid request body - missing diffCreated', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toBe('Invalid request body for copilot stats') + }) + + it('should return 400 for invalid request body - missing diffAccepted', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toBe('Invalid request body for copilot stats') + }) + + it('should return 400 when upstream Sim Agent returns error', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockResolvedValueOnce({ + ok: false, + json: () => Promise.resolve({ error: 'Invalid message ID' }), + }) + + const req = createMockRequest('POST', { + messageId: 'invalid-message', + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData).toEqual({ success: false, error: 'Invalid message ID' }) + }) + + it('should handle upstream error with message field', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockResolvedValueOnce({ + ok: false, + json: () => Promise.resolve({ message: 'Rate limit exceeded' }), + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData).toEqual({ success: false, error: 'Rate limit exceeded' }) + }) + + it('should handle upstream error with no JSON response', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockResolvedValueOnce({ + ok: false, + json: () => Promise.reject(new Error('Not JSON')), + }) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData).toEqual({ success: false, error: 'Upstream error' }) + }) + + it('should handle network errors gracefully', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockRejectedValueOnce(new Error('Network error')) + + const req = createMockRequest('POST', { + messageId: 'message-123', + diffCreated: true, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(500) + const responseData = await response.json() + expect(responseData.error).toBe('Failed to forward copilot stats') + }) + + it('should handle JSON parsing errors in request body', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + const req = new NextRequest('http://localhost:3000/api/copilot/stats', { + method: 'POST', + body: '{invalid-json', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(400) + const responseData = await response.json() + expect(responseData.error).toBe('Invalid request body for copilot stats') + }) + + it('should forward stats with diffCreated=false and diffAccepted=false', async () => { + const { authenticateCopilotRequestSessionOnly } = await import( + '@/lib/copilot/request-helpers' + ) + vi.mocked(authenticateCopilotRequestSessionOnly).mockResolvedValueOnce({ + userId: 'user-123', + isAuthenticated: true, + }) + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + + const req = createMockRequest('POST', { + messageId: 'message-456', + diffCreated: false, + diffAccepted: false, + }) + + const { POST } = await import('@/app/api/copilot/stats/route') + const response = await POST(req) + + expect(response.status).toBe(200) + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + messageId: 'message-456', + diffCreated: false, + diffAccepted: false, + }), + }) + ) + }) + }) +}) diff --git a/apps/sim/app/api/copilot/tools/mark-complete/route.ts b/apps/sim/app/api/copilot/tools/mark-complete/route.ts index 93bfef7d2d..1ada484e5b 100644 --- a/apps/sim/app/api/copilot/tools/mark-complete/route.ts +++ b/apps/sim/app/api/copilot/tools/mark-complete/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants' @@ -9,7 +10,6 @@ import { createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotMarkToolCompleteAPI') diff --git a/apps/sim/app/api/copilot/training/examples/route.ts b/apps/sim/app/api/copilot/training/examples/route.ts index 7d735427df..1d23793cd7 100644 --- a/apps/sim/app/api/copilot/training/examples/route.ts +++ b/apps/sim/app/api/copilot/training/examples/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotTrainingExamplesAPI') diff --git a/apps/sim/app/api/copilot/training/route.ts b/apps/sim/app/api/copilot/training/route.ts index aed162af6a..4ff955eee0 100644 --- a/apps/sim/app/api/copilot/training/route.ts +++ b/apps/sim/app/api/copilot/training/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotTrainingAPI') diff --git a/apps/sim/app/api/copilot/user-models/route.ts b/apps/sim/app/api/copilot/user-models/route.ts index 5708b3f602..d98d49baaa 100644 --- a/apps/sim/app/api/copilot/user-models/route.ts +++ b/apps/sim/app/api/copilot/user-models/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { db } from '@/../../packages/db' import { settings } from '@/../../packages/db/schema' diff --git a/apps/sim/app/api/creators/[id]/route.ts b/apps/sim/app/api/creators/[id]/route.ts index c55b915cbf..326504b969 100644 --- a/apps/sim/app/api/creators/[id]/route.ts +++ b/apps/sim/app/api/creators/[id]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { member, templateCreators } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CreatorProfileByIdAPI') diff --git a/apps/sim/app/api/creators/[id]/verify/route.ts b/apps/sim/app/api/creators/[id]/verify/route.ts index 2bb13115bd..45cd2dc0b0 100644 --- a/apps/sim/app/api/creators/[id]/verify/route.ts +++ b/apps/sim/app/api/creators/[id]/verify/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { templateCreators, user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CreatorVerificationAPI') diff --git a/apps/sim/app/api/creators/route.ts b/apps/sim/app/api/creators/route.ts index 96548e83e3..1113de3d45 100644 --- a/apps/sim/app/api/creators/route.ts +++ b/apps/sim/app/api/creators/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { member, templateCreators } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import type { CreatorProfileDetails } from '@/app/_types/creator-profile' const logger = createLogger('CreatorProfilesAPI') diff --git a/apps/sim/app/api/cron/renew-subscriptions/route.ts b/apps/sim/app/api/cron/renew-subscriptions/route.ts index 501fdfdc43..b60afc84cc 100644 --- a/apps/sim/app/api/cron/renew-subscriptions/route.ts +++ b/apps/sim/app/api/cron/renew-subscriptions/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { webhook as webhookTable, workflow as workflowTable } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' const logger = createLogger('TeamsSubscriptionRenewal') diff --git a/apps/sim/app/api/environment/route.ts b/apps/sim/app/api/environment/route.ts index 6425305f3b..5e7fa4006e 100644 --- a/apps/sim/app/api/environment/route.ts +++ b/apps/sim/app/api/environment/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { environment } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import type { EnvironmentVariable } from '@/stores/settings/environment/types' const logger = createLogger('EnvironmentAPI') diff --git a/apps/sim/app/api/files/authorization.ts b/apps/sim/app/api/files/authorization.ts index 65b3381a19..3366e5830d 100644 --- a/apps/sim/app/api/files/authorization.ts +++ b/apps/sim/app/api/files/authorization.ts @@ -1,7 +1,7 @@ import { db } from '@sim/db' import { document, workspaceFile } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, like, or } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { getFileMetadata } from '@/lib/uploads' import type { StorageContext } from '@/lib/uploads/config' import { diff --git a/apps/sim/app/api/files/delete/route.ts b/apps/sim/app/api/files/delete/route.ts index 2122181a32..1a5f491388 100644 --- a/apps/sim/app/api/files/delete/route.ts +++ b/apps/sim/app/api/files/delete/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { createLogger } from '@/lib/logs/console/logger' import type { StorageContext } from '@/lib/uploads/config' import { deleteFile, hasCloudStorage } from '@/lib/uploads/core/storage-service' import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file-utils' diff --git a/apps/sim/app/api/files/download/route.ts b/apps/sim/app/api/files/download/route.ts index 38de9b4021..bd718ed8f4 100644 --- a/apps/sim/app/api/files/download/route.ts +++ b/apps/sim/app/api/files/download/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { createLogger } from '@/lib/logs/console/logger' import type { StorageContext } from '@/lib/uploads/config' import { hasCloudStorage } from '@/lib/uploads/core/storage-service' import { verifyFileAccess } from '@/app/api/files/authorization' diff --git a/apps/sim/app/api/files/multipart/route.ts b/apps/sim/app/api/files/multipart/route.ts index ee8c36547a..02ba826fc9 100644 --- a/apps/sim/app/api/files/multipart/route.ts +++ b/apps/sim/app/api/files/multipart/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { getStorageConfig, getStorageProvider, diff --git a/apps/sim/app/api/files/parse/route.ts b/apps/sim/app/api/files/parse/route.ts index d02d077325..4e4d54f18b 100644 --- a/apps/sim/app/api/files/parse/route.ts +++ b/apps/sim/app/api/files/parse/route.ts @@ -2,12 +2,12 @@ import { Buffer } from 'buffer' import { createHash } from 'crypto' import fsPromises, { readFile } from 'fs/promises' import path from 'path' +import { createLogger } from '@sim/logger' import binaryExtensionsList from 'binary-extensions' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { createPinnedUrl, validateUrlWithDNS } from '@/lib/core/security/input-validation' import { isSupportedFileType, parseFile } from '@/lib/file-parsers' -import { createLogger } from '@/lib/logs/console/logger' import { isUsingCloudStorage, type StorageContext, StorageService } from '@/lib/uploads' import { UPLOAD_DIR_SERVER } from '@/lib/uploads/core/setup.server' import { getFileMetadataByKey } from '@/lib/uploads/server/metadata' diff --git a/apps/sim/app/api/files/presigned/batch/route.ts b/apps/sim/app/api/files/presigned/batch/route.ts index 4f52f334dd..f2aa4aa320 100644 --- a/apps/sim/app/api/files/presigned/batch/route.ts +++ b/apps/sim/app/api/files/presigned/batch/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import type { StorageContext } from '@/lib/uploads/config' import { USE_BLOB_STORAGE } from '@/lib/uploads/config' import { diff --git a/apps/sim/app/api/files/presigned/route.ts b/apps/sim/app/api/files/presigned/route.ts index adbd439701..6068140660 100644 --- a/apps/sim/app/api/files/presigned/route.ts +++ b/apps/sim/app/api/files/presigned/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { CopilotFiles } from '@/lib/uploads' import type { StorageContext } from '@/lib/uploads/config' import { USE_BLOB_STORAGE } from '@/lib/uploads/config' diff --git a/apps/sim/app/api/files/serve/[...path]/route.ts b/apps/sim/app/api/files/serve/[...path]/route.ts index 0843a2e968..e339615f87 100644 --- a/apps/sim/app/api/files/serve/[...path]/route.ts +++ b/apps/sim/app/api/files/serve/[...path]/route.ts @@ -1,8 +1,8 @@ import { readFile } from 'fs/promises' +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { createLogger } from '@/lib/logs/console/logger' import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads' import type { StorageContext } from '@/lib/uploads/config' import { downloadFile } from '@/lib/uploads/core/storage-service' diff --git a/apps/sim/app/api/files/upload/route.ts b/apps/sim/app/api/files/upload/route.ts index c23f46ec84..eca3667926 100644 --- a/apps/sim/app/api/files/upload/route.ts +++ b/apps/sim/app/api/files/upload/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { sanitizeFileName } from '@/executor/constants' import '@/lib/uploads/core/setup.server' import { getSession } from '@/lib/auth' diff --git a/apps/sim/app/api/files/utils.ts b/apps/sim/app/api/files/utils.ts index 50286bdba2..953c9b8989 100644 --- a/apps/sim/app/api/files/utils.ts +++ b/apps/sim/app/api/files/utils.ts @@ -1,7 +1,7 @@ import { existsSync } from 'fs' import { join, resolve, sep } from 'path' +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { UPLOAD_DIR } from '@/lib/uploads/config' import { sanitizeFileKey } from '@/lib/uploads/utils/file-utils' diff --git a/apps/sim/app/api/folders/[id]/duplicate/route.ts b/apps/sim/app/api/folders/[id]/duplicate/route.ts index 914485a3b6..60b3e99961 100644 --- a/apps/sim/app/api/folders/[id]/duplicate/route.ts +++ b/apps/sim/app/api/folders/[id]/duplicate/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/folders/[id]/route.ts b/apps/sim/app/api/folders/[id]/route.ts index 773427e230..ebd44f9816 100644 --- a/apps/sim/app/api/folders/[id]/route.ts +++ b/apps/sim/app/api/folders/[id]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('FoldersIDAPI') diff --git a/apps/sim/app/api/folders/route.ts b/apps/sim/app/api/folders/route.ts index 050d8524d7..e976f1a945 100644 --- a/apps/sim/app/api/folders/route.ts +++ b/apps/sim/app/api/folders/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { workflowFolder } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, asc, desc, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('FoldersAPI') diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index d49cfbb6f2..12bf26a7ab 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -82,7 +82,7 @@ vi.mock('@/lib/execution/isolated-vm', () => ({ }), })) -vi.mock('@/lib/logs/console/logger', () => ({ +vi.mock('@sim/logger', () => ({ createLogger: vi.fn(() => ({ info: vi.fn(), error: vi.fn(), diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index ce42d5e67f..cb1da555af 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -1,10 +1,10 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { isE2bEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { executeInE2B } from '@/lib/execution/e2b' import { executeInIsolatedVM } from '@/lib/execution/isolated-vm' import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages' -import { createLogger } from '@/lib/logs/console/logger' import { escapeRegExp, normalizeName, REFERENCE } from '@/executor/constants' import { createEnvVarPattern, diff --git a/apps/sim/app/api/guardrails/validate/route.ts b/apps/sim/app/api/guardrails/validate/route.ts index 93be5e8b47..5f47383390 100644 --- a/apps/sim/app/api/guardrails/validate/route.ts +++ b/apps/sim/app/api/guardrails/validate/route.ts @@ -1,10 +1,10 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' import { validateHallucination } from '@/lib/guardrails/validate_hallucination' import { validateJson } from '@/lib/guardrails/validate_json' import { validatePII } from '@/lib/guardrails/validate_pii' import { validateRegex } from '@/lib/guardrails/validate_regex' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('GuardrailsValidateAPI') diff --git a/apps/sim/app/api/help/route.ts b/apps/sim/app/api/help/route.ts index 27a9d03afa..ca3d040c2a 100644 --- a/apps/sim/app/api/help/route.ts +++ b/apps/sim/app/api/help/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { renderHelpConfirmationEmail } from '@/components/emails' @@ -5,7 +6,6 @@ import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { generateRequestId } from '@/lib/core/utils/request' import { getEmailDomain } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' diff --git a/apps/sim/app/api/jobs/[jobId]/route.ts b/apps/sim/app/api/jobs/[jobId]/route.ts index 58c01d103a..74dc52407d 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { runs } from '@trigger.dev/sdk' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createErrorResponse } from '@/app/api/workflows/utils' const logger = createLogger('TaskStatusAPI') @@ -31,7 +31,7 @@ export async function GET( const payload = run.payload as any if (payload?.workflowId) { - const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions') + const { verifyWorkflowAccess } = await import('@/socket/middleware/permissions') const accessCheck = await verifyWorkflowAccess(authenticatedUserId, payload.workflowId) if (!accessCheck.hasAccess) { logger.warn(`[${requestId}] User ${authenticatedUserId} denied access to task ${taskId}`, { diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts index 1df8cde317..08c02d508b 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts @@ -1,9 +1,9 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { deleteChunk, updateChunk } from '@/lib/knowledge/chunks/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkChunkAccess } from '@/app/api/knowledge/utils' const logger = createLogger('ChunkByIdAPI') @@ -100,7 +100,12 @@ export async function PUT( try { const validatedData = UpdateChunkSchema.parse(body) - const updatedChunk = await updateChunk(chunkId, validatedData, requestId) + const updatedChunk = await updateChunk( + chunkId, + validatedData, + requestId, + accessCheck.knowledgeBase?.workspaceId + ) logger.info( `[${requestId}] Chunk updated: ${chunkId} in document ${documentId} in knowledge base ${knowledgeBaseId}` diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts index 284f069378..c5d8590097 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { batchChunkOperation, createChunk, queryChunks } from '@/lib/knowledge/chunks/service' -import { createLogger } from '@/lib/logs/console/logger' import { getUserId } from '@/app/api/auth/oauth/utils' import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils' import { calculateCost } from '@/providers/utils' @@ -184,7 +184,8 @@ export async function POST( documentId, docTags, validatedData, - requestId + requestId, + accessCheck.knowledgeBase?.workspaceId ) let cost = null diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts index 6e5495aa7c..9d3ad15219 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -8,7 +9,6 @@ import { retryDocumentProcessing, updateDocument, } from '@/lib/knowledge/documents/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils' const logger = createLogger('DocumentByIdAPI') diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts index 5403857e48..e228255cd9 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/tag-definitions/route.ts @@ -1,4 +1,5 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -10,7 +11,6 @@ import { getDocumentTagDefinitions, } from '@/lib/knowledge/tags/service' import type { BulkTagDefinitionsData } from '@/lib/knowledge/tags/types' -import { createLogger } from '@/lib/logs/console/logger' import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/knowledge/[id]/documents/route.ts b/apps/sim/app/api/knowledge/[id]/documents/route.ts index 4c57d21bd1..7aba07d610 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/route.ts @@ -1,4 +1,5 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -11,7 +12,6 @@ import { processDocumentsWithQueue, } from '@/lib/knowledge/documents/service' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' -import { createLogger } from '@/lib/logs/console/logger' import { getUserId } from '@/app/api/auth/oauth/utils' import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' diff --git a/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts b/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts index fc17e86fec..b328b7d5b6 100644 --- a/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts +++ b/apps/sim/app/api/knowledge/[id]/next-available-slot/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { getNextAvailableSlot, getTagDefinitions } from '@/lib/knowledge/tags/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' const logger = createLogger('NextAvailableSlotAPI') diff --git a/apps/sim/app/api/knowledge/[id]/route.ts b/apps/sim/app/api/knowledge/[id]/route.ts index 4096779f99..a26273b4a4 100644 --- a/apps/sim/app/api/knowledge/[id]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -7,7 +8,6 @@ import { getKnowledgeBaseById, updateKnowledgeBase, } from '@/lib/knowledge/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' const logger = createLogger('KnowledgeBaseByIdAPI') diff --git a/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts b/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts index a0f18b54e5..a141461ec0 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-definitions/[tagId]/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { deleteTagDefinition } from '@/lib/knowledge/tags/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts index 6e45c64d13..09f1fc7873 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants' import { createTagDefinition, getTagDefinitions } from '@/lib/knowledge/tags/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts b/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts index 55ef74ef67..788ae89758 100644 --- a/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts +++ b/apps/sim/app/api/knowledge/[id]/tag-usage/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { getTagUsage } from '@/lib/knowledge/tags/service' -import { createLogger } from '@/lib/logs/console/logger' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/knowledge/route.ts b/apps/sim/app/api/knowledge/route.ts index fbcba90ec7..3910fca333 100644 --- a/apps/sim/app/api/knowledge/route.ts +++ b/apps/sim/app/api/knowledge/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { createKnowledgeBase, getKnowledgeBases } from '@/lib/knowledge/service' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('KnowledgeBaseAPI') diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index 4172ebc2d6..6e3f584029 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' @@ -5,7 +6,6 @@ import { ALL_TAG_SLOTS } from '@/lib/knowledge/constants' import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service' import { buildUndefinedTagsError, validateTagValue } from '@/lib/knowledge/tags/utils' import type { StructuredFilter } from '@/lib/knowledge/types' -import { createLogger } from '@/lib/logs/console/logger' import { estimateTokenCount } from '@/lib/tokenization/estimators' import { getUserId } from '@/app/api/auth/oauth/utils' import { @@ -183,11 +183,11 @@ export async function POST(request: NextRequest) { ) } - // Generate query embedding only if query is provided + const workspaceId = accessChecks.find((ac) => ac?.hasAccess)?.knowledgeBase?.workspaceId + const hasQuery = validatedData.query && validatedData.query.trim().length > 0 - // Start embedding generation early and await when needed const queryEmbeddingPromise = hasQuery - ? generateSearchEmbedding(validatedData.query!) + ? generateSearchEmbedding(validatedData.query!, undefined, workspaceId) : Promise.resolve(null) // Check if any requested knowledge bases were not accessible diff --git a/apps/sim/app/api/knowledge/search/utils.test.ts b/apps/sim/app/api/knowledge/search/utils.test.ts index 882d658534..53ceeaa0ae 100644 --- a/apps/sim/app/api/knowledge/search/utils.test.ts +++ b/apps/sim/app/api/knowledge/search/utils.test.ts @@ -7,7 +7,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('drizzle-orm') -vi.mock('@/lib/logs/console/logger', () => ({ +vi.mock('@sim/logger', () => ({ createLogger: vi.fn(() => ({ info: vi.fn(), debug: vi.fn(), diff --git a/apps/sim/app/api/knowledge/search/utils.ts b/apps/sim/app/api/knowledge/search/utils.ts index 74b47664d0..3eba10f911 100644 --- a/apps/sim/app/api/knowledge/search/utils.ts +++ b/apps/sim/app/api/knowledge/search/utils.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { document, embedding } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import type { StructuredFilter } from '@/lib/knowledge/types' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('KnowledgeSearchUtils') diff --git a/apps/sim/app/api/knowledge/utils.ts b/apps/sim/app/api/knowledge/utils.ts index b1f796a1a6..b829709172 100644 --- a/apps/sim/app/api/knowledge/utils.ts +++ b/apps/sim/app/api/knowledge/utils.ts @@ -99,7 +99,7 @@ export interface EmbeddingData { export interface KnowledgeBaseAccessResult { hasAccess: true - knowledgeBase: Pick + knowledgeBase: Pick } export interface KnowledgeBaseAccessDenied { @@ -113,7 +113,7 @@ export type KnowledgeBaseAccessCheck = KnowledgeBaseAccessResult | KnowledgeBase export interface DocumentAccessResult { hasAccess: true document: DocumentData - knowledgeBase: Pick + knowledgeBase: Pick } export interface DocumentAccessDenied { @@ -128,7 +128,7 @@ export interface ChunkAccessResult { hasAccess: true chunk: EmbeddingData document: DocumentData - knowledgeBase: Pick + knowledgeBase: Pick } export interface ChunkAccessDenied { diff --git a/apps/sim/app/api/logs/[id]/route.ts b/apps/sim/app/api/logs/[id]/route.ts index 466868c080..c97764784b 100644 --- a/apps/sim/app/api/logs/[id]/route.ts +++ b/apps/sim/app/api/logs/[id]/route.ts @@ -5,11 +5,11 @@ import { workflowDeploymentVersion, workflowExecutionLogs, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('LogDetailsByIdAPI') diff --git a/apps/sim/app/api/logs/cleanup/route.ts b/apps/sim/app/api/logs/cleanup/route.ts index 7f55cfd373..853fee2002 100644 --- a/apps/sim/app/api/logs/cleanup/route.ts +++ b/apps/sim/app/api/logs/cleanup/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { subscription, user, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray, lt, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { snapshotService } from '@/lib/logs/execution/snapshot/service' import { isUsingCloudStorage, StorageService } from '@/lib/uploads' diff --git a/apps/sim/app/api/logs/execution/[executionId]/route.ts b/apps/sim/app/api/logs/execution/[executionId]/route.ts index d785a76c8d..2c3cd164ef 100644 --- a/apps/sim/app/api/logs/execution/[executionId]/route.ts +++ b/apps/sim/app/api/logs/execution/[executionId]/route.ts @@ -5,11 +5,11 @@ import { workflowExecutionLogs, workflowExecutionSnapshots, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('LogsByExecutionIdAPI') diff --git a/apps/sim/app/api/logs/export/route.ts b/apps/sim/app/api/logs/export/route.ts index 5b98331132..e43e62b458 100644 --- a/apps/sim/app/api/logs/export/route.ts +++ b/apps/sim/app/api/logs/export/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters' const logger = createLogger('LogsExportAPI') diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index 6f1811fd64..cfc14ac39b 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -6,12 +6,12 @@ import { workflowDeploymentVersion, workflowExecutionLogs, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq, isNotNull, isNull, or, type SQL, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters' const logger = createLogger('LogsAPI') diff --git a/apps/sim/app/api/logs/triggers/route.ts b/apps/sim/app/api/logs/triggers/route.ts index 1d241cd5b3..dfbcd1001c 100644 --- a/apps/sim/app/api/logs/triggers/route.ts +++ b/apps/sim/app/api/logs/triggers/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { permissions, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, isNotNull, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TriggersAPI') diff --git a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts index ba58b0ba7a..2e3474e68d 100644 --- a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpServerStatusConfig } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/servers/[id]/route.ts b/apps/sim/app/api/mcp/servers/[id]/route.ts index 40c35fdb73..fc986ccc9f 100644 --- a/apps/sim/app/api/mcp/servers/[id]/route.ts +++ b/apps/sim/app/api/mcp/servers/[id]/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import { validateMcpServerUrl } from '@/lib/mcp/url-validator' diff --git a/apps/sim/app/api/mcp/servers/route.ts b/apps/sim/app/api/mcp/servers/route.ts index 8dc3db4dc9..d8ca7c93ff 100644 --- a/apps/sim/app/api/mcp/servers/route.ts +++ b/apps/sim/app/api/mcp/servers/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpTransport } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/servers/test-connection/route.ts b/apps/sim/app/api/mcp/servers/test-connection/route.ts index 1c4add215e..cc52ec88e4 100644 --- a/apps/sim/app/api/mcp/servers/test-connection/route.ts +++ b/apps/sim/app/api/mcp/servers/test-connection/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' -import { createLogger } from '@/lib/logs/console/logger' import { McpClient } from '@/lib/mcp/client' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import type { McpServerConfig, McpTransport } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/tools/discover/route.ts b/apps/sim/app/api/mcp/tools/discover/route.ts index 8ae3dfb59a..de88cbb28b 100644 --- a/apps/sim/app/api/mcp/tools/discover/route.ts +++ b/apps/sim/app/api/mcp/tools/discover/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpToolDiscoveryResponse } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/tools/execute/route.ts b/apps/sim/app/api/mcp/tools/execute/route.ts index d58d0bea24..1bcdf6488e 100644 --- a/apps/sim/app/api/mcp/tools/execute/route.ts +++ b/apps/sim/app/api/mcp/tools/execute/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpTool, McpToolCall, McpToolResult } from '@/lib/mcp/types' diff --git a/apps/sim/app/api/mcp/tools/stored/route.ts b/apps/sim/app/api/mcp/tools/stored/route.ts index b3906954aa..09519aa677 100644 --- a/apps/sim/app/api/mcp/tools/stored/route.ts +++ b/apps/sim/app/api/mcp/tools/stored/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { workflow, workflowBlocks } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { withMcpAuth } from '@/lib/mcp/middleware' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' diff --git a/apps/sim/app/api/memory/[id]/route.ts b/apps/sim/app/api/memory/[id]/route.ts index 516a907fc7..617979ef16 100644 --- a/apps/sim/app/api/memory/[id]/route.ts +++ b/apps/sim/app/api/memory/[id]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { memory, permissions, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('MemoryByIdAPI') diff --git a/apps/sim/app/api/memory/route.ts b/apps/sim/app/api/memory/route.ts index e6afab0f9a..fe159b9664 100644 --- a/apps/sim/app/api/memory/route.ts +++ b/apps/sim/app/api/memory/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { memory, permissions, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, isNull, like } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('MemoryAPI') diff --git a/apps/sim/app/api/notifications/poll/route.ts b/apps/sim/app/api/notifications/poll/route.ts index 00157a0bf2..cbc0246096 100644 --- a/apps/sim/app/api/notifications/poll/route.ts +++ b/apps/sim/app/api/notifications/poll/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { createLogger } from '@/lib/logs/console/logger' import { pollInactivityAlerts } from '@/lib/notifications/inactivity-polling' const logger = createLogger('InactivityAlertPoll') diff --git a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts index d6e53dbbdc..bf9332caa0 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts @@ -11,12 +11,12 @@ import { type WorkspaceInvitationStatus, workspaceInvitation, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { requireStripeClient } from '@/lib/billing/stripe-client' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationInvitation') diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.ts b/apps/sim/app/api/organizations/[id]/invitations/route.ts index 5a61a277a5..46cdabea9d 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/route.ts @@ -9,6 +9,7 @@ import { workspace, workspaceInvitation, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -22,7 +23,6 @@ import { validateSeatAvailability, } from '@/lib/billing/validation/seat-management' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts index 577f2730cf..6793a5d13b 100644 --- a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { member, user, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { getUserUsageData } from '@/lib/billing/core/usage' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationMemberAPI') diff --git a/apps/sim/app/api/organizations/[id]/members/route.ts b/apps/sim/app/api/organizations/[id]/members/route.ts index 9b54abf607..4ada7c2ba8 100644 --- a/apps/sim/app/api/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/route.ts @@ -1,6 +1,7 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { invitation, member, organization, user, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getEmailSubject, renderInvitationEmail } from '@/components/emails/render-email' @@ -8,7 +9,6 @@ import { getSession } from '@/lib/auth' import { getUserUsageData } from '@/lib/billing/core/usage' import { validateSeatAvailability } from '@/lib/billing/validation/seat-management' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { quickValidateEmail } from '@/lib/messaging/email/validation' diff --git a/apps/sim/app/api/organizations/[id]/route.ts b/apps/sim/app/api/organizations/[id]/route.ts index 65e9743942..b528e60256 100644 --- a/apps/sim/app/api/organizations/[id]/route.ts +++ b/apps/sim/app/api/organizations/[id]/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { member, organization } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, ne } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { getOrganizationSeatAnalytics, getOrganizationSeatInfo, } from '@/lib/billing/validation/seat-management' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationAPI') diff --git a/apps/sim/app/api/organizations/[id]/seats/route.ts b/apps/sim/app/api/organizations/[id]/seats/route.ts index 9f877e3b36..eaadf5717a 100644 --- a/apps/sim/app/api/organizations/[id]/seats/route.ts +++ b/apps/sim/app/api/organizations/[id]/seats/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { member, organization, subscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -7,7 +8,6 @@ import { getSession } from '@/lib/auth' import { getPlanPricing } from '@/lib/billing/core/billing' import { requireStripeClient } from '@/lib/billing/stripe-client' import { isBillingEnabled } from '@/lib/core/config/feature-flags' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationSeatsAPI') diff --git a/apps/sim/app/api/organizations/[id]/workspaces/route.ts b/apps/sim/app/api/organizations/[id]/workspaces/route.ts index b4f3fb5079..6669c8a8b4 100644 --- a/apps/sim/app/api/organizations/[id]/workspaces/route.ts +++ b/apps/sim/app/api/organizations/[id]/workspaces/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { member, permissions, user, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationWorkspacesAPI') diff --git a/apps/sim/app/api/organizations/route.ts b/apps/sim/app/api/organizations/route.ts index 81ae107c3b..28cc31183c 100644 --- a/apps/sim/app/api/organizations/route.ts +++ b/apps/sim/app/api/organizations/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { member, organization } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, or } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createOrganizationForTeamPlan } from '@/lib/billing/organization' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationsAPI') diff --git a/apps/sim/app/api/providers/ollama/models/route.ts b/apps/sim/app/api/providers/ollama/models/route.ts index d135afc9e9..f396f21b0a 100644 --- a/apps/sim/app/api/providers/ollama/models/route.ts +++ b/apps/sim/app/api/providers/ollama/models/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import type { ModelsObject } from '@/providers/ollama/types' const logger = createLogger('OllamaModelsAPI') diff --git a/apps/sim/app/api/providers/openrouter/models/route.ts b/apps/sim/app/api/providers/openrouter/models/route.ts index 2703870aa8..cf3419e5ba 100644 --- a/apps/sim/app/api/providers/openrouter/models/route.ts +++ b/apps/sim/app/api/providers/openrouter/models/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { filterBlacklistedModels } from '@/providers/utils' const logger = createLogger('OpenRouterModelsAPI') diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index 04910ed1c8..a78a5f999d 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -1,13 +1,12 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { StreamingExecution } from '@/executor/types' import { executeProviderRequest } from '@/providers' -import { getApiKey } from '@/providers/utils' const logger = createLogger('ProvidersAPI') @@ -80,23 +79,20 @@ export async function POST(request: NextRequest) { verbosity, }) - let finalApiKey: string + let finalApiKey: string | undefined = apiKey try { if (provider === 'vertex' && vertexCredential) { finalApiKey = await resolveVertexCredential(requestId, vertexCredential) - } else { - finalApiKey = getApiKey(provider, model, apiKey) } } catch (error) { - logger.error(`[${requestId}] Failed to get API key:`, { + logger.error(`[${requestId}] Failed to resolve Vertex credential:`, { provider, model, error: error instanceof Error ? error.message : String(error), - hasProvidedApiKey: !!apiKey, hasVertexCredential: !!vertexCredential, }) return NextResponse.json( - { error: error instanceof Error ? error.message : 'API key error' }, + { error: error instanceof Error ? error.message : 'Credential error' }, { status: 400 } ) } @@ -108,7 +104,6 @@ export async function POST(request: NextRequest) { hasApiKey: !!finalApiKey, }) - // Execute provider request directly with the managed key const response = await executeProviderRequest(provider, { model, systemPrompt, diff --git a/apps/sim/app/api/providers/vllm/models/route.ts b/apps/sim/app/api/providers/vllm/models/route.ts index f9f76332ea..65bbccbbe1 100644 --- a/apps/sim/app/api/providers/vllm/models/route.ts +++ b/apps/sim/app/api/providers/vllm/models/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('VLLMModelsAPI') diff --git a/apps/sim/app/api/proxy/image/route.ts b/apps/sim/app/api/proxy/image/route.ts index 70d1fd81d3..1caf695fb9 100644 --- a/apps/sim/app/api/proxy/image/route.ts +++ b/apps/sim/app/api/proxy/image/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateImageUrl } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ImageProxyAPI') diff --git a/apps/sim/app/api/proxy/route.ts b/apps/sim/app/api/proxy/route.ts index cb223aebd7..24702aa48f 100644 --- a/apps/sim/app/api/proxy/route.ts +++ b/apps/sim/app/api/proxy/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { z } from 'zod' @@ -7,7 +8,6 @@ import { isDev } from '@/lib/core/config/feature-flags' import { createPinnedUrl, validateUrlWithDNS } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { executeTool } from '@/tools' import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils' diff --git a/apps/sim/app/api/proxy/stt/route.ts b/apps/sim/app/api/proxy/stt/route.ts index d2a94cda74..a7b05f19a1 100644 --- a/apps/sim/app/api/proxy/stt/route.ts +++ b/apps/sim/app/api/proxy/stt/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { extractAudioFromVideo, isVideoFile } from '@/lib/audio/extractor' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { createLogger } from '@/lib/logs/console/logger' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import type { UserFile } from '@/executor/types' import type { TranscriptSegment } from '@/tools/stt/types' diff --git a/apps/sim/app/api/proxy/tts/route.ts b/apps/sim/app/api/proxy/tts/route.ts index f3db903461..1ae734f21b 100644 --- a/apps/sim/app/api/proxy/tts/route.ts +++ b/apps/sim/app/api/proxy/tts/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { StorageService } from '@/lib/uploads' const logger = createLogger('ProxyTTSAPI') diff --git a/apps/sim/app/api/proxy/tts/stream/route.ts b/apps/sim/app/api/proxy/tts/stream/route.ts index 316c0d0a0a..35b045fc94 100644 --- a/apps/sim/app/api/proxy/tts/stream/route.ts +++ b/apps/sim/app/api/proxy/tts/stream/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { env } from '@/lib/core/config/env' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { validateAuthToken } from '@/app/api/chat/utils' const logger = createLogger('ProxyTTSStreamAPI') diff --git a/apps/sim/app/api/proxy/tts/unified/route.ts b/apps/sim/app/api/proxy/tts/unified/route.ts index 827dfae61c..cf9464452b 100644 --- a/apps/sim/app/api/proxy/tts/unified/route.ts +++ b/apps/sim/app/api/proxy/tts/unified/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { StorageService } from '@/lib/uploads' import type { AzureTtsParams, diff --git a/apps/sim/app/api/proxy/video/route.ts b/apps/sim/app/api/proxy/video/route.ts index 9aa4091ef6..9074a290a1 100644 --- a/apps/sim/app/api/proxy/video/route.ts +++ b/apps/sim/app/api/proxy/video/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { createLogger } from '@/lib/logs/console/logger' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import type { UserFile } from '@/executor/types' import type { VideoRequestBody } from '@/tools/video/types' diff --git a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts index 8fcdfe59d8..9feef89bf3 100644 --- a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts +++ b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' import { preprocessExecution } from '@/lib/execution/preprocessing' -import { createLogger } from '@/lib/logs/console/logger' import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' diff --git a/apps/sim/app/api/resume/[workflowId]/[executionId]/route.ts b/apps/sim/app/api/resume/[workflowId]/[executionId]/route.ts index f27ad36c78..1e3cc4b53e 100644 --- a/apps/sim/app/api/resume/[workflowId]/[executionId]/route.ts +++ b/apps/sim/app/api/resume/[workflowId]/[executionId]/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' diff --git a/apps/sim/app/api/schedules/[id]/route.test.ts b/apps/sim/app/api/schedules/[id]/route.test.ts index a24fb07a78..0ab1195884 100644 --- a/apps/sim/app/api/schedules/[id]/route.test.ts +++ b/apps/sim/app/api/schedules/[id]/route.test.ts @@ -43,7 +43,7 @@ vi.mock('@/lib/core/utils/request', () => ({ generateRequestId: () => 'test-request-id', })) -vi.mock('@/lib/logs/console/logger', () => ({ +vi.mock('@sim/logger', () => ({ createLogger: () => ({ info: vi.fn(), warn: vi.fn(), diff --git a/apps/sim/app/api/schedules/[id]/route.ts b/apps/sim/app/api/schedules/[id]/route.ts index c3aa491e00..031358ba25 100644 --- a/apps/sim/app/api/schedules/[id]/route.ts +++ b/apps/sim/app/api/schedules/[id]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workflowSchedule } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { validateCronExpression } from '@/lib/workflows/schedules/utils' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index 5254028d61..cadad529f5 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -1,11 +1,11 @@ import { db, workflowSchedule } from '@sim/db' +import { createLogger } from '@sim/logger' import { tasks } from '@trigger.dev/sdk' import { and, eq, isNull, lt, lte, not, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { executeScheduleJob } from '@/background/schedule-execution' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/schedules/route.test.ts b/apps/sim/app/api/schedules/route.test.ts index ac1ece178d..986e731138 100644 --- a/apps/sim/app/api/schedules/route.test.ts +++ b/apps/sim/app/api/schedules/route.test.ts @@ -40,7 +40,7 @@ vi.mock('@/lib/core/utils/request', () => ({ generateRequestId: () => 'test-request-id', })) -vi.mock('@/lib/logs/console/logger', () => ({ +vi.mock('@sim/logger', () => ({ createLogger: () => ({ info: vi.fn(), warn: vi.fn(), @@ -144,7 +144,7 @@ describe('Schedule GET API', () => { it('indicates disabled schedule with failures', async () => { mockDbChain([ [{ userId: 'user-1', workspaceId: null }], - [{ id: 'sched-1', status: 'disabled', failedCount: 10 }], + [{ id: 'sched-1', status: 'disabled', failedCount: 100 }], ]) const res = await GET(createRequest('http://test/api/schedules?workflowId=wf-1')) diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index 07f8cbc952..3b6ba81864 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { workflow, workflowSchedule } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('ScheduledAPI') diff --git a/apps/sim/app/api/status/route.ts b/apps/sim/app/api/status/route.ts index ebc5e98a9a..8c7a28a174 100644 --- a/apps/sim/app/api/status/route.ts +++ b/apps/sim/app/api/status/route.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import type { IncidentIOWidgetResponse, StatusResponse, StatusType } from '@/app/api/status/types' const logger = createLogger('StatusAPI') diff --git a/apps/sim/app/api/telemetry/route.ts b/apps/sim/app/api/telemetry/route.ts index e7bc3bc893..1eae8acdc9 100644 --- a/apps/sim/app/api/telemetry/route.ts +++ b/apps/sim/app/api/telemetry/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' import { isProd } from '@/lib/core/config/feature-flags' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TelemetryAPI') diff --git a/apps/sim/app/api/templates/[id]/approve/route.ts b/apps/sim/app/api/templates/[id]/approve/route.ts index 9f212829bf..c15c1916ee 100644 --- a/apps/sim/app/api/templates/[id]/approve/route.ts +++ b/apps/sim/app/api/templates/[id]/approve/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { verifySuperUser } from '@/lib/templates/permissions' const logger = createLogger('TemplateApprovalAPI') diff --git a/apps/sim/app/api/templates/[id]/og-image/route.ts b/apps/sim/app/api/templates/[id]/og-image/route.ts index f628096b21..f6b2dd94bf 100644 --- a/apps/sim/app/api/templates/[id]/og-image/route.ts +++ b/apps/sim/app/api/templates/[id]/og-image/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { verifyTemplateOwnership } from '@/lib/templates/permissions' import { uploadFile } from '@/lib/uploads/core/storage-service' import { isValidPng } from '@/lib/uploads/utils/validation' diff --git a/apps/sim/app/api/templates/[id]/reject/route.ts b/apps/sim/app/api/templates/[id]/reject/route.ts index 425f907833..af5ed2e12b 100644 --- a/apps/sim/app/api/templates/[id]/reject/route.ts +++ b/apps/sim/app/api/templates/[id]/reject/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { verifySuperUser } from '@/lib/templates/permissions' const logger = createLogger('TemplateRejectionAPI') diff --git a/apps/sim/app/api/templates/[id]/route.ts b/apps/sim/app/api/templates/[id]/route.ts index d19731f49e..6feef0f32f 100644 --- a/apps/sim/app/api/templates/[id]/route.ts +++ b/apps/sim/app/api/templates/[id]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { templateCreators, templates, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { extractRequiredCredentials, sanitizeCredentials, @@ -169,7 +169,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ if (creatorId !== undefined) updateData.creatorId = creatorId if (updateState && template.workflowId) { - const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions') + const { verifyWorkflowAccess } = await import('@/socket/middleware/permissions') const { hasAccess: hasWorkflowAccess } = await verifyWorkflowAccess( session.user.id, template.workflowId diff --git a/apps/sim/app/api/templates/[id]/star/route.ts b/apps/sim/app/api/templates/[id]/star/route.ts index 26fd3a9b4a..d7e23c9d45 100644 --- a/apps/sim/app/api/templates/[id]/star/route.ts +++ b/apps/sim/app/api/templates/[id]/star/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { templateStars, templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TemplateStarAPI') diff --git a/apps/sim/app/api/templates/[id]/use/route.ts b/apps/sim/app/api/templates/[id]/use/route.ts index d32572611b..3ffb9f5b27 100644 --- a/apps/sim/app/api/templates/[id]/use/route.ts +++ b/apps/sim/app/api/templates/[id]/use/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { templates, workflow, workflowDeploymentVersion } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { regenerateWorkflowStateIds } from '@/lib/workflows/persistence/utils' const logger = createLogger('TemplateUseAPI') diff --git a/apps/sim/app/api/templates/approved/sanitized/route.ts b/apps/sim/app/api/templates/approved/sanitized/route.ts index d8ddb6a97f..2b6fad9652 100644 --- a/apps/sim/app/api/templates/approved/sanitized/route.ts +++ b/apps/sim/app/api/templates/approved/sanitized/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { checkInternalApiKey } from '@/lib/copilot/utils' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer' const logger = createLogger('TemplatesSanitizedAPI') diff --git a/apps/sim/app/api/templates/route.ts b/apps/sim/app/api/templates/route.ts index 2cbbc9469c..7177aa0050 100644 --- a/apps/sim/app/api/templates/route.ts +++ b/apps/sim/app/api/templates/route.ts @@ -7,13 +7,13 @@ import { workflow, workflowDeploymentVersion, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq, ilike, or, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { extractRequiredCredentials, sanitizeCredentials, diff --git a/apps/sim/app/api/tools/asana/add-comment/route.ts b/apps/sim/app/api/tools/asana/add-comment/route.ts index bd00e151c0..b6ef38d944 100644 --- a/apps/sim/app/api/tools/asana/add-comment/route.ts +++ b/apps/sim/app/api/tools/asana/add-comment/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/create-task/route.ts b/apps/sim/app/api/tools/asana/create-task/route.ts index 69200e6d90..41cd673295 100644 --- a/apps/sim/app/api/tools/asana/create-task/route.ts +++ b/apps/sim/app/api/tools/asana/create-task/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/get-projects/route.ts b/apps/sim/app/api/tools/asana/get-projects/route.ts index f26da3fd9a..c57fae722b 100644 --- a/apps/sim/app/api/tools/asana/get-projects/route.ts +++ b/apps/sim/app/api/tools/asana/get-projects/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/get-task/route.ts b/apps/sim/app/api/tools/asana/get-task/route.ts index bcc459e4c5..d60902fec2 100644 --- a/apps/sim/app/api/tools/asana/get-task/route.ts +++ b/apps/sim/app/api/tools/asana/get-task/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/search-tasks/route.ts b/apps/sim/app/api/tools/asana/search-tasks/route.ts index 397b9b07ce..d9b7e82886 100644 --- a/apps/sim/app/api/tools/asana/search-tasks/route.ts +++ b/apps/sim/app/api/tools/asana/search-tasks/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/asana/update-task/route.ts b/apps/sim/app/api/tools/asana/update-task/route.ts index e83cc5ef9b..3bc242a293 100644 --- a/apps/sim/app/api/tools/asana/update-task/route.ts +++ b/apps/sim/app/api/tools/asana/update-task/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/confluence/attachment/route.ts b/apps/sim/app/api/tools/confluence/attachment/route.ts index bfaa2e82a2..7b55dc719a 100644 --- a/apps/sim/app/api/tools/confluence/attachment/route.ts +++ b/apps/sim/app/api/tools/confluence/attachment/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceAttachmentAPI') diff --git a/apps/sim/app/api/tools/confluence/attachments/route.ts b/apps/sim/app/api/tools/confluence/attachments/route.ts index 869c3b988d..6154f3e08b 100644 --- a/apps/sim/app/api/tools/confluence/attachments/route.ts +++ b/apps/sim/app/api/tools/confluence/attachments/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceAttachmentsAPI') diff --git a/apps/sim/app/api/tools/confluence/comment/route.ts b/apps/sim/app/api/tools/confluence/comment/route.ts index 94fd963af4..c94ac85e98 100644 --- a/apps/sim/app/api/tools/confluence/comment/route.ts +++ b/apps/sim/app/api/tools/confluence/comment/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { z } from 'zod' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceCommentAPI') diff --git a/apps/sim/app/api/tools/confluence/comments/route.ts b/apps/sim/app/api/tools/confluence/comments/route.ts index b9717e73d3..eac22a2b2e 100644 --- a/apps/sim/app/api/tools/confluence/comments/route.ts +++ b/apps/sim/app/api/tools/confluence/comments/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceCommentsAPI') diff --git a/apps/sim/app/api/tools/confluence/create-page/route.ts b/apps/sim/app/api/tools/confluence/create-page/route.ts index c50acf93e8..218b4ff61f 100644 --- a/apps/sim/app/api/tools/confluence/create-page/route.ts +++ b/apps/sim/app/api/tools/confluence/create-page/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceCreatePageAPI') diff --git a/apps/sim/app/api/tools/confluence/labels/route.ts b/apps/sim/app/api/tools/confluence/labels/route.ts index 6ab71167a5..557c542d12 100644 --- a/apps/sim/app/api/tools/confluence/labels/route.ts +++ b/apps/sim/app/api/tools/confluence/labels/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceLabelsAPI') diff --git a/apps/sim/app/api/tools/confluence/page/route.ts b/apps/sim/app/api/tools/confluence/page/route.ts index d9fdceb0eb..685eefffd2 100644 --- a/apps/sim/app/api/tools/confluence/page/route.ts +++ b/apps/sim/app/api/tools/confluence/page/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { z } from 'zod' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluencePageAPI') diff --git a/apps/sim/app/api/tools/confluence/pages/route.ts b/apps/sim/app/api/tools/confluence/pages/route.ts index 67fed46f71..e83198ffee 100644 --- a/apps/sim/app/api/tools/confluence/pages/route.ts +++ b/apps/sim/app/api/tools/confluence/pages/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluencePagesAPI') diff --git a/apps/sim/app/api/tools/confluence/search/route.ts b/apps/sim/app/api/tools/confluence/search/route.ts index 1c522898c6..3782aace3e 100644 --- a/apps/sim/app/api/tools/confluence/search/route.ts +++ b/apps/sim/app/api/tools/confluence/search/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/confluence/space/route.ts b/apps/sim/app/api/tools/confluence/space/route.ts index 75bf8b324d..bda98ce6bc 100644 --- a/apps/sim/app/api/tools/confluence/space/route.ts +++ b/apps/sim/app/api/tools/confluence/space/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceSpaceAPI') diff --git a/apps/sim/app/api/tools/confluence/spaces/route.ts b/apps/sim/app/api/tools/confluence/spaces/route.ts index 028257e975..6d66aae097 100644 --- a/apps/sim/app/api/tools/confluence/spaces/route.ts +++ b/apps/sim/app/api/tools/confluence/spaces/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getConfluenceCloudId } from '@/tools/confluence/utils' const logger = createLogger('ConfluenceSpacesAPI') diff --git a/apps/sim/app/api/tools/confluence/upload-attachment/route.ts b/apps/sim/app/api/tools/confluence/upload-attachment/route.ts index 21e9f75ef8..7487b6ee50 100644 --- a/apps/sim/app/api/tools/confluence/upload-attachment/route.ts +++ b/apps/sim/app/api/tools/confluence/upload-attachment/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { getConfluenceCloudId } from '@/tools/confluence/utils' diff --git a/apps/sim/app/api/tools/custom/route.test.ts b/apps/sim/app/api/tools/custom/route.test.ts index 5894171a24..88f61ca129 100644 --- a/apps/sim/app/api/tools/custom/route.test.ts +++ b/apps/sim/app/api/tools/custom/route.test.ts @@ -209,7 +209,7 @@ describe('Custom Tools API Routes', () => { })) // Mock logger - vi.doMock('@/lib/logs/console/logger', () => ({ + vi.doMock('@sim/logger', () => ({ createLogger: vi.fn().mockReturnValue({ info: vi.fn(), error: vi.fn(), diff --git a/apps/sim/app/api/tools/custom/route.ts b/apps/sim/app/api/tools/custom/route.ts index 0bb32c5148..e3c68302d0 100644 --- a/apps/sim/app/api/tools/custom/route.ts +++ b/apps/sim/app/api/tools/custom/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { customTools, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/tools/discord/channels/route.ts b/apps/sim/app/api/tools/discord/channels/route.ts index 25eed7c5c0..23b33dd762 100644 --- a/apps/sim/app/api/tools/discord/channels/route.ts +++ b/apps/sim/app/api/tools/discord/channels/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateNumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' interface DiscordChannel { id: string diff --git a/apps/sim/app/api/tools/discord/send-message/route.ts b/apps/sim/app/api/tools/discord/send-message/route.ts index ef6df171dc..cb113a460b 100644 --- a/apps/sim/app/api/tools/discord/send-message/route.ts +++ b/apps/sim/app/api/tools/discord/send-message/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateNumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/discord/servers/route.ts b/apps/sim/app/api/tools/discord/servers/route.ts index c7fa8c7561..c589ad4b20 100644 --- a/apps/sim/app/api/tools/discord/servers/route.ts +++ b/apps/sim/app/api/tools/discord/servers/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateNumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' interface DiscordServer { id: string diff --git a/apps/sim/app/api/tools/drive/file/route.ts b/apps/sim/app/api/tools/drive/file/route.ts index 62eb9686c7..931253b04b 100644 --- a/apps/sim/app/api/tools/drive/file/route.ts +++ b/apps/sim/app/api/tools/drive/file/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/drive/files/route.ts b/apps/sim/app/api/tools/drive/files/route.ts index fa4bb1596f..5584fe392f 100644 --- a/apps/sim/app/api/tools/drive/files/route.ts +++ b/apps/sim/app/api/tools/drive/files/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/add-label/route.ts b/apps/sim/app/api/tools/gmail/add-label/route.ts index a8f1391804..5654c10f5e 100644 --- a/apps/sim/app/api/tools/gmail/add-label/route.ts +++ b/apps/sim/app/api/tools/gmail/add-label/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/archive/route.ts b/apps/sim/app/api/tools/gmail/archive/route.ts index 2f62d211ed..604d5bbce5 100644 --- a/apps/sim/app/api/tools/gmail/archive/route.ts +++ b/apps/sim/app/api/tools/gmail/archive/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/delete/route.ts b/apps/sim/app/api/tools/gmail/delete/route.ts index ce3779c9d6..08730b1cfa 100644 --- a/apps/sim/app/api/tools/gmail/delete/route.ts +++ b/apps/sim/app/api/tools/gmail/delete/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/draft/route.ts b/apps/sim/app/api/tools/gmail/draft/route.ts index 90f849b0d0..e852d43786 100644 --- a/apps/sim/app/api/tools/gmail/draft/route.ts +++ b/apps/sim/app/api/tools/gmail/draft/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { diff --git a/apps/sim/app/api/tools/gmail/label/route.ts b/apps/sim/app/api/tools/gmail/label/route.ts index c7042034c3..7994c91fd0 100644 --- a/apps/sim/app/api/tools/gmail/label/route.ts +++ b/apps/sim/app/api/tools/gmail/label/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/labels/route.ts b/apps/sim/app/api/tools/gmail/labels/route.ts index 945db0afa4..36d9040ca4 100644 --- a/apps/sim/app/api/tools/gmail/labels/route.ts +++ b/apps/sim/app/api/tools/gmail/labels/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/mark-read/route.ts b/apps/sim/app/api/tools/gmail/mark-read/route.ts index 3525869567..8e0592ee8d 100644 --- a/apps/sim/app/api/tools/gmail/mark-read/route.ts +++ b/apps/sim/app/api/tools/gmail/mark-read/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/mark-unread/route.ts b/apps/sim/app/api/tools/gmail/mark-unread/route.ts index 17aca8e7fc..901023fcdb 100644 --- a/apps/sim/app/api/tools/gmail/mark-unread/route.ts +++ b/apps/sim/app/api/tools/gmail/mark-unread/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/move/route.ts b/apps/sim/app/api/tools/gmail/move/route.ts index 358768fe3e..37af235ff5 100644 --- a/apps/sim/app/api/tools/gmail/move/route.ts +++ b/apps/sim/app/api/tools/gmail/move/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/remove-label/route.ts b/apps/sim/app/api/tools/gmail/remove-label/route.ts index 74e179c910..a6bcd0e4c8 100644 --- a/apps/sim/app/api/tools/gmail/remove-label/route.ts +++ b/apps/sim/app/api/tools/gmail/remove-label/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/gmail/send/route.ts b/apps/sim/app/api/tools/gmail/send/route.ts index d9c3dc9ecc..f624eba41f 100644 --- a/apps/sim/app/api/tools/gmail/send/route.ts +++ b/apps/sim/app/api/tools/gmail/send/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { diff --git a/apps/sim/app/api/tools/gmail/unarchive/route.ts b/apps/sim/app/api/tools/gmail/unarchive/route.ts index 28bf5b879c..1479430c4a 100644 --- a/apps/sim/app/api/tools/gmail/unarchive/route.ts +++ b/apps/sim/app/api/tools/gmail/unarchive/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/google_calendar/calendars/route.ts b/apps/sim/app/api/tools/google_calendar/calendars/route.ts index 77b6291bfe..f934d6dd41 100644 --- a/apps/sim/app/api/tools/google_calendar/calendars/route.ts +++ b/apps/sim/app/api/tools/google_calendar/calendars/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { isUuidV4 } from '@/executor/constants' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/google_drive/upload/route.ts b/apps/sim/app/api/tools/google_drive/upload/route.ts index 13acfd3b25..fc9b26a8ea 100644 --- a/apps/sim/app/api/tools/google_drive/upload/route.ts +++ b/apps/sim/app/api/tools/google_drive/upload/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { diff --git a/apps/sim/app/api/tools/jira/issue/route.ts b/apps/sim/app/api/tools/jira/issue/route.ts index 5e2f8e6436..d77d98ffe3 100644 --- a/apps/sim/app/api/tools/jira/issue/route.ts +++ b/apps/sim/app/api/tools/jira/issue/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getJiraCloudId } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jira/issues/route.ts b/apps/sim/app/api/tools/jira/issues/route.ts index 68368596cc..cc5f7a4cc9 100644 --- a/apps/sim/app/api/tools/jira/issues/route.ts +++ b/apps/sim/app/api/tools/jira/issues/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getJiraCloudId } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jira/projects/route.ts b/apps/sim/app/api/tools/jira/projects/route.ts index 9551cde78d..e85e4cc947 100644 --- a/apps/sim/app/api/tools/jira/projects/route.ts +++ b/apps/sim/app/api/tools/jira/projects/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getJiraCloudId } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jira/update/route.ts b/apps/sim/app/api/tools/jira/update/route.ts index 1a924faa23..b1e67f9531 100644 --- a/apps/sim/app/api/tools/jira/update/route.ts +++ b/apps/sim/app/api/tools/jira/update/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { z } from 'zod' import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getJiraCloudId } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/jira/write/route.ts b/apps/sim/app/api/tools/jira/write/route.ts index 0eb1148926..c80434ceb4 100644 --- a/apps/sim/app/api/tools/jira/write/route.ts +++ b/apps/sim/app/api/tools/jira/write/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { getJiraCloudId } from '@/tools/jira/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/linear/projects/route.ts b/apps/sim/app/api/tools/linear/projects/route.ts index 51863961d9..9e0ff73354 100644 --- a/apps/sim/app/api/tools/linear/projects/route.ts +++ b/apps/sim/app/api/tools/linear/projects/route.ts @@ -1,9 +1,9 @@ import type { Project } from '@linear/sdk' import { LinearClient } from '@linear/sdk' +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/linear/teams/route.ts b/apps/sim/app/api/tools/linear/teams/route.ts index cf1e36ce20..ee82c15425 100644 --- a/apps/sim/app/api/tools/linear/teams/route.ts +++ b/apps/sim/app/api/tools/linear/teams/route.ts @@ -1,9 +1,9 @@ import type { Team } from '@linear/sdk' import { LinearClient } from '@linear/sdk' +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/mail/send/route.ts b/apps/sim/app/api/tools/mail/send/route.ts index ede1dc9a64..d98b9b9bc0 100644 --- a/apps/sim/app/api/tools/mail/send/route.ts +++ b/apps/sim/app/api/tools/mail/send/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { Resend } from 'resend' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts index 0d07ca4335..0dc1fa8a0a 100644 --- a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts index 356f92475a..a0113647a6 100644 --- a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts index 4dc4513535..a903815abe 100644 --- a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts b/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts index 69b075399f..67566ad8a8 100644 --- a/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts +++ b/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { PlannerTask } from '@/tools/microsoft_planner/types' diff --git a/apps/sim/app/api/tools/microsoft_teams/delete_chat_message/route.ts b/apps/sim/app/api/tools/microsoft_teams/delete_chat_message/route.ts index 44f91f1e8f..a604ca445d 100644 --- a/apps/sim/app/api/tools/microsoft_teams/delete_chat_message/route.ts +++ b/apps/sim/app/api/tools/microsoft_teams/delete_chat_message/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts b/apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts index a3f703b30e..3c21168a0e 100644 --- a/apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts +++ b/apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { resolveMentionsForChannel, type TeamsMention } from '@/tools/microsoft_teams/utils' diff --git a/apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts b/apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts index 635f48f917..0682429e7c 100644 --- a/apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts +++ b/apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { resolveMentionsForChat, type TeamsMention } from '@/tools/microsoft_teams/utils' diff --git a/apps/sim/app/api/tools/mistral/parse/route.ts b/apps/sim/app/api/tools/mistral/parse/route.ts index d3cd52c582..b31029d1bc 100644 --- a/apps/sim/app/api/tools/mistral/parse/route.ts +++ b/apps/sim/app/api/tools/mistral/parse/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { StorageService } from '@/lib/uploads' import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file-utils' import { verifyFileAccess } from '@/app/api/files/authorization' diff --git a/apps/sim/app/api/tools/mongodb/delete/route.ts b/apps/sim/app/api/tools/mongodb/delete/route.ts index 56058881a9..b634677258 100644 --- a/apps/sim/app/api/tools/mongodb/delete/route.ts +++ b/apps/sim/app/api/tools/mongodb/delete/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' 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') diff --git a/apps/sim/app/api/tools/mongodb/execute/route.ts b/apps/sim/app/api/tools/mongodb/execute/route.ts index bb1b2f0cda..afae959759 100644 --- a/apps/sim/app/api/tools/mongodb/execute/route.ts +++ b/apps/sim/app/api/tools/mongodb/execute/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' 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') diff --git a/apps/sim/app/api/tools/mongodb/insert/route.ts b/apps/sim/app/api/tools/mongodb/insert/route.ts index b71a9efdd8..fd350ef3e5 100644 --- a/apps/sim/app/api/tools/mongodb/insert/route.ts +++ b/apps/sim/app/api/tools/mongodb/insert/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' 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') diff --git a/apps/sim/app/api/tools/mongodb/query/route.ts b/apps/sim/app/api/tools/mongodb/query/route.ts index 1c451e5bc6..ae8276dea6 100644 --- a/apps/sim/app/api/tools/mongodb/query/route.ts +++ b/apps/sim/app/api/tools/mongodb/query/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' 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') diff --git a/apps/sim/app/api/tools/mongodb/update/route.ts b/apps/sim/app/api/tools/mongodb/update/route.ts index c4a420bf66..ac24d55396 100644 --- a/apps/sim/app/api/tools/mongodb/update/route.ts +++ b/apps/sim/app/api/tools/mongodb/update/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' 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') diff --git a/apps/sim/app/api/tools/mysql/delete/route.ts b/apps/sim/app/api/tools/mysql/delete/route.ts index 4387ab1277..4b33288036 100644 --- a/apps/sim/app/api/tools/mysql/delete/route.ts +++ b/apps/sim/app/api/tools/mysql/delete/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { buildDeleteQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLDeleteAPI') diff --git a/apps/sim/app/api/tools/mysql/execute/route.ts b/apps/sim/app/api/tools/mysql/execute/route.ts index eea3bd142b..8e4ac396af 100644 --- a/apps/sim/app/api/tools/mysql/execute/route.ts +++ b/apps/sim/app/api/tools/mysql/execute/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLExecuteAPI') diff --git a/apps/sim/app/api/tools/mysql/insert/route.ts b/apps/sim/app/api/tools/mysql/insert/route.ts index 04e30a4ad6..5e8fd4674b 100644 --- a/apps/sim/app/api/tools/mysql/insert/route.ts +++ b/apps/sim/app/api/tools/mysql/insert/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { buildInsertQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLInsertAPI') diff --git a/apps/sim/app/api/tools/mysql/query/route.ts b/apps/sim/app/api/tools/mysql/query/route.ts index 791b67dacb..ad8535ce29 100644 --- a/apps/sim/app/api/tools/mysql/query/route.ts +++ b/apps/sim/app/api/tools/mysql/query/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLQueryAPI') diff --git a/apps/sim/app/api/tools/mysql/update/route.ts b/apps/sim/app/api/tools/mysql/update/route.ts index f1b8e8c64a..c196bf9248 100644 --- a/apps/sim/app/api/tools/mysql/update/route.ts +++ b/apps/sim/app/api/tools/mysql/update/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { buildUpdateQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' const logger = createLogger('MySQLUpdateAPI') diff --git a/apps/sim/app/api/tools/neo4j/create/route.ts b/apps/sim/app/api/tools/neo4j/create/route.ts index a8d8ed12a5..3fb66142a3 100644 --- a/apps/sim/app/api/tools/neo4j/create/route.ts +++ b/apps/sim/app/api/tools/neo4j/create/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/delete/route.ts b/apps/sim/app/api/tools/neo4j/delete/route.ts index baa639b229..e010fe8b6a 100644 --- a/apps/sim/app/api/tools/neo4j/delete/route.ts +++ b/apps/sim/app/api/tools/neo4j/delete/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createNeo4jDriver, validateCypherQuery } from '@/app/api/tools/neo4j/utils' const logger = createLogger('Neo4jDeleteAPI') diff --git a/apps/sim/app/api/tools/neo4j/execute/route.ts b/apps/sim/app/api/tools/neo4j/execute/route.ts index 91eb8379b7..79d98975ff 100644 --- a/apps/sim/app/api/tools/neo4j/execute/route.ts +++ b/apps/sim/app/api/tools/neo4j/execute/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/merge/route.ts b/apps/sim/app/api/tools/neo4j/merge/route.ts index 3e43762bb7..28f00a7e06 100644 --- a/apps/sim/app/api/tools/neo4j/merge/route.ts +++ b/apps/sim/app/api/tools/neo4j/merge/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/query/route.ts b/apps/sim/app/api/tools/neo4j/query/route.ts index f5b8084959..84dd3cb511 100644 --- a/apps/sim/app/api/tools/neo4j/query/route.ts +++ b/apps/sim/app/api/tools/neo4j/query/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/neo4j/update/route.ts b/apps/sim/app/api/tools/neo4j/update/route.ts index 1f0d84015e..e5f2bfb76c 100644 --- a/apps/sim/app/api/tools/neo4j/update/route.ts +++ b/apps/sim/app/api/tools/neo4j/update/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { convertNeo4jTypesToJSON, createNeo4jDriver, diff --git a/apps/sim/app/api/tools/onedrive/files/route.ts b/apps/sim/app/api/tools/onedrive/files/route.ts index 0a551f5bd8..c894834576 100644 --- a/apps/sim/app/api/tools/onedrive/files/route.ts +++ b/apps/sim/app/api/tools/onedrive/files/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onedrive/folder/route.ts b/apps/sim/app/api/tools/onedrive/folder/route.ts index 7f93d0e3fd..2cf68fa533 100644 --- a/apps/sim/app/api/tools/onedrive/folder/route.ts +++ b/apps/sim/app/api/tools/onedrive/folder/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onedrive/folders/route.ts b/apps/sim/app/api/tools/onedrive/folders/route.ts index 61adffde4d..1eac6c2678 100644 --- a/apps/sim/app/api/tools/onedrive/folders/route.ts +++ b/apps/sim/app/api/tools/onedrive/folders/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/onedrive/upload/route.ts b/apps/sim/app/api/tools/onedrive/upload/route.ts index c5c4d29ae0..3e7fef64f4 100644 --- a/apps/sim/app/api/tools/onedrive/upload/route.ts +++ b/apps/sim/app/api/tools/onedrive/upload/route.ts @@ -1,10 +1,10 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import * as XLSX from 'xlsx' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getExtensionFromMimeType, processSingleFileToUserFile, diff --git a/apps/sim/app/api/tools/outlook/copy/route.ts b/apps/sim/app/api/tools/outlook/copy/route.ts index b4435931b5..0766b97322 100644 --- a/apps/sim/app/api/tools/outlook/copy/route.ts +++ b/apps/sim/app/api/tools/outlook/copy/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/delete/route.ts b/apps/sim/app/api/tools/outlook/delete/route.ts index 7e47dafb02..b5f8fafce5 100644 --- a/apps/sim/app/api/tools/outlook/delete/route.ts +++ b/apps/sim/app/api/tools/outlook/delete/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/draft/route.ts b/apps/sim/app/api/tools/outlook/draft/route.ts index 16ed64c02b..6dfdcec5c4 100644 --- a/apps/sim/app/api/tools/outlook/draft/route.ts +++ b/apps/sim/app/api/tools/outlook/draft/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/outlook/folders/route.ts b/apps/sim/app/api/tools/outlook/folders/route.ts index 91395e6846..7be86ebff0 100644 --- a/apps/sim/app/api/tools/outlook/folders/route.ts +++ b/apps/sim/app/api/tools/outlook/folders/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/mark-read/route.ts b/apps/sim/app/api/tools/outlook/mark-read/route.ts index 1873249d7a..b8b26515c6 100644 --- a/apps/sim/app/api/tools/outlook/mark-read/route.ts +++ b/apps/sim/app/api/tools/outlook/mark-read/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/mark-unread/route.ts b/apps/sim/app/api/tools/outlook/mark-unread/route.ts index 7b52941b52..f9fef10cc9 100644 --- a/apps/sim/app/api/tools/outlook/mark-unread/route.ts +++ b/apps/sim/app/api/tools/outlook/mark-unread/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/move/route.ts b/apps/sim/app/api/tools/outlook/move/route.ts index 5cdbc56f76..62f432db8f 100644 --- a/apps/sim/app/api/tools/outlook/move/route.ts +++ b/apps/sim/app/api/tools/outlook/move/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/outlook/send/route.ts b/apps/sim/app/api/tools/outlook/send/route.ts index 59293f535b..e3544171e3 100644 --- a/apps/sim/app/api/tools/outlook/send/route.ts +++ b/apps/sim/app/api/tools/outlook/send/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/postgresql/delete/route.ts b/apps/sim/app/api/tools/postgresql/delete/route.ts index ea6ce401b4..f18df3db1a 100644 --- a/apps/sim/app/api/tools/postgresql/delete/route.ts +++ b/apps/sim/app/api/tools/postgresql/delete/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createPostgresConnection, executeDelete } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLDeleteAPI') diff --git a/apps/sim/app/api/tools/postgresql/execute/route.ts b/apps/sim/app/api/tools/postgresql/execute/route.ts index c66db63947..403823e367 100644 --- a/apps/sim/app/api/tools/postgresql/execute/route.ts +++ b/apps/sim/app/api/tools/postgresql/execute/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createPostgresConnection, executeQuery, diff --git a/apps/sim/app/api/tools/postgresql/insert/route.ts b/apps/sim/app/api/tools/postgresql/insert/route.ts index e3193e29f0..e01cc9fe27 100644 --- a/apps/sim/app/api/tools/postgresql/insert/route.ts +++ b/apps/sim/app/api/tools/postgresql/insert/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createPostgresConnection, executeInsert } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLInsertAPI') diff --git a/apps/sim/app/api/tools/postgresql/query/route.ts b/apps/sim/app/api/tools/postgresql/query/route.ts index 135b044b65..a6ee4bad26 100644 --- a/apps/sim/app/api/tools/postgresql/query/route.ts +++ b/apps/sim/app/api/tools/postgresql/query/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createPostgresConnection, executeQuery } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLQueryAPI') diff --git a/apps/sim/app/api/tools/postgresql/update/route.ts b/apps/sim/app/api/tools/postgresql/update/route.ts index 70933d74f3..862f6dffb4 100644 --- a/apps/sim/app/api/tools/postgresql/update/route.ts +++ b/apps/sim/app/api/tools/postgresql/update/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createPostgresConnection, executeUpdate } from '@/app/api/tools/postgresql/utils' const logger = createLogger('PostgreSQLUpdateAPI') diff --git a/apps/sim/app/api/tools/rds/delete/route.ts b/apps/sim/app/api/tools/rds/delete/route.ts index f26ab21c4a..e309796660 100644 --- a/apps/sim/app/api/tools/rds/delete/route.ts +++ b/apps/sim/app/api/tools/rds/delete/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createRdsClient, executeDelete } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSDeleteAPI') diff --git a/apps/sim/app/api/tools/rds/execute/route.ts b/apps/sim/app/api/tools/rds/execute/route.ts index 73463fc065..9510d40886 100644 --- a/apps/sim/app/api/tools/rds/execute/route.ts +++ b/apps/sim/app/api/tools/rds/execute/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createRdsClient, executeStatement } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSExecuteAPI') diff --git a/apps/sim/app/api/tools/rds/insert/route.ts b/apps/sim/app/api/tools/rds/insert/route.ts index a00f184cf8..6f766d423f 100644 --- a/apps/sim/app/api/tools/rds/insert/route.ts +++ b/apps/sim/app/api/tools/rds/insert/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createRdsClient, executeInsert } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSInsertAPI') diff --git a/apps/sim/app/api/tools/rds/query/route.ts b/apps/sim/app/api/tools/rds/query/route.ts index 5c9d022630..81d972d47a 100644 --- a/apps/sim/app/api/tools/rds/query/route.ts +++ b/apps/sim/app/api/tools/rds/query/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createRdsClient, executeStatement, validateQuery } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSQueryAPI') diff --git a/apps/sim/app/api/tools/rds/update/route.ts b/apps/sim/app/api/tools/rds/update/route.ts index 307a6e19f0..9648574e2c 100644 --- a/apps/sim/app/api/tools/rds/update/route.ts +++ b/apps/sim/app/api/tools/rds/update/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createRdsClient, executeUpdate } from '@/app/api/tools/rds/utils' const logger = createLogger('RDSUpdateAPI') diff --git a/apps/sim/app/api/tools/s3/copy-object/route.ts b/apps/sim/app/api/tools/s3/copy-object/route.ts index 4c96284be0..888aaf6308 100644 --- a/apps/sim/app/api/tools/s3/copy-object/route.ts +++ b/apps/sim/app/api/tools/s3/copy-object/route.ts @@ -1,9 +1,9 @@ import { CopyObjectCommand, type ObjectCannedACL, S3Client } from '@aws-sdk/client-s3' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/s3/delete-object/route.ts b/apps/sim/app/api/tools/s3/delete-object/route.ts index 1a3566a020..4319a45240 100644 --- a/apps/sim/app/api/tools/s3/delete-object/route.ts +++ b/apps/sim/app/api/tools/s3/delete-object/route.ts @@ -1,9 +1,9 @@ import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/s3/list-objects/route.ts b/apps/sim/app/api/tools/s3/list-objects/route.ts index 2d773fe30b..2b43592bde 100644 --- a/apps/sim/app/api/tools/s3/list-objects/route.ts +++ b/apps/sim/app/api/tools/s3/list-objects/route.ts @@ -1,9 +1,9 @@ import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/s3/put-object/route.ts b/apps/sim/app/api/tools/s3/put-object/route.ts index 3d84b9a23a..2f7aced28b 100644 --- a/apps/sim/app/api/tools/s3/put-object/route.ts +++ b/apps/sim/app/api/tools/s3/put-object/route.ts @@ -1,9 +1,9 @@ import { type ObjectCannedACL, PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/search/route.ts b/apps/sim/app/api/tools/search/route.ts index 2ae8af018a..8c0bca85a3 100644 --- a/apps/sim/app/api/tools/search/route.ts +++ b/apps/sim/app/api/tools/search/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { SEARCH_TOOL_COST } from '@/lib/billing/constants' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { executeTool } from '@/tools' const logger = createLogger('search') @@ -39,8 +39,10 @@ export async function POST(request: NextRequest) { const body = await request.json() const validated = SearchRequestSchema.parse(body) - if (!env.EXA_API_KEY) { - logger.error(`[${requestId}] EXA_API_KEY not configured`) + const exaApiKey = env.EXA_API_KEY + + if (!exaApiKey) { + logger.error(`[${requestId}] No Exa API key available`) return NextResponse.json( { success: false, error: 'Search service not configured' }, { status: 503 } @@ -57,7 +59,7 @@ export async function POST(request: NextRequest) { type: 'auto', useAutoprompt: true, highlights: true, - apiKey: env.EXA_API_KEY, + apiKey: exaApiKey, }) if (!result.success) { diff --git a/apps/sim/app/api/tools/sftp/delete/route.ts b/apps/sim/app/api/tools/sftp/delete/route.ts index b551af72bb..e1a5aec459 100644 --- a/apps/sim/app/api/tools/sftp/delete/route.ts +++ b/apps/sim/app/api/tools/sftp/delete/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createSftpConnection, getFileType, diff --git a/apps/sim/app/api/tools/sftp/download/route.ts b/apps/sim/app/api/tools/sftp/download/route.ts index 3c5e343a07..cc954b90cf 100644 --- a/apps/sim/app/api/tools/sftp/download/route.ts +++ b/apps/sim/app/api/tools/sftp/download/route.ts @@ -1,9 +1,9 @@ import path from 'path' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createSftpConnection, getSftp, isPathSafe, sanitizePath } from '@/app/api/tools/sftp/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/sftp/list/route.ts b/apps/sim/app/api/tools/sftp/list/route.ts index 23c349e415..5d70f344b2 100644 --- a/apps/sim/app/api/tools/sftp/list/route.ts +++ b/apps/sim/app/api/tools/sftp/list/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createSftpConnection, getFileType, diff --git a/apps/sim/app/api/tools/sftp/mkdir/route.ts b/apps/sim/app/api/tools/sftp/mkdir/route.ts index ab74ae42ac..783c9a8d93 100644 --- a/apps/sim/app/api/tools/sftp/mkdir/route.ts +++ b/apps/sim/app/api/tools/sftp/mkdir/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { SFTPWrapper } from 'ssh2' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createSftpConnection, getSftp, diff --git a/apps/sim/app/api/tools/sftp/upload/route.ts b/apps/sim/app/api/tools/sftp/upload/route.ts index a0154f755f..b1f9f0622a 100644 --- a/apps/sim/app/api/tools/sftp/upload/route.ts +++ b/apps/sim/app/api/tools/sftp/upload/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { diff --git a/apps/sim/app/api/tools/sharepoint/site/route.ts b/apps/sim/app/api/tools/sharepoint/site/route.ts index ffa8d74b61..2ffecce942 100644 --- a/apps/sim/app/api/tools/sharepoint/site/route.ts +++ b/apps/sim/app/api/tools/sharepoint/site/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/sharepoint/sites/route.ts b/apps/sim/app/api/tools/sharepoint/sites/route.ts index 2f39cc0496..7e98bf6212 100644 --- a/apps/sim/app/api/tools/sharepoint/sites/route.ts +++ b/apps/sim/app/api/tools/sharepoint/sites/route.ts @@ -1,11 +1,11 @@ import { randomUUID } from 'crypto' import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateAlphanumericId } from '@/lib/core/security/input-validation' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { SharepointSite } from '@/tools/sharepoint/types' diff --git a/apps/sim/app/api/tools/sharepoint/upload/route.ts b/apps/sim/app/api/tools/sharepoint/upload/route.ts index 00a4c7633e..a1a69e3c9d 100644 --- a/apps/sim/app/api/tools/sharepoint/upload/route.ts +++ b/apps/sim/app/api/tools/sharepoint/upload/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/slack/channels/route.ts b/apps/sim/app/api/tools/slack/channels/route.ts index d48d066130..b96badeba3 100644 --- a/apps/sim/app/api/tools/slack/channels/route.ts +++ b/apps/sim/app/api/tools/slack/channels/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/slack/read-messages/route.ts b/apps/sim/app/api/tools/slack/read-messages/route.ts index c0a87c3cf2..43cc77e05d 100644 --- a/apps/sim/app/api/tools/slack/read-messages/route.ts +++ b/apps/sim/app/api/tools/slack/read-messages/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { openDMChannel } from '../utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/slack/send-message/route.ts b/apps/sim/app/api/tools/slack/send-message/route.ts index 592721d0de..21d5983209 100644 --- a/apps/sim/app/api/tools/slack/send-message/route.ts +++ b/apps/sim/app/api/tools/slack/send-message/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { sendSlackMessage } from '../utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/slack/update-message/route.ts b/apps/sim/app/api/tools/slack/update-message/route.ts index d89f9b0a9f..a30d52a838 100644 --- a/apps/sim/app/api/tools/slack/update-message/route.ts +++ b/apps/sim/app/api/tools/slack/update-message/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/slack/users/route.ts b/apps/sim/app/api/tools/slack/users/route.ts index 666800f56e..7b11620585 100644 --- a/apps/sim/app/api/tools/slack/users/route.ts +++ b/apps/sim/app/api/tools/slack/users/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/slack/utils.ts b/apps/sim/app/api/tools/slack/utils.ts index b52d734203..4a18071bfc 100644 --- a/apps/sim/app/api/tools/slack/utils.ts +++ b/apps/sim/app/api/tools/slack/utils.ts @@ -1,4 +1,4 @@ -import type { Logger } from '@/lib/logs/console/logger' +import type { Logger } from '@sim/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/sms/send/route.ts b/apps/sim/app/api/tools/sms/send/route.ts index d16a1c57c1..6468dde307 100644 --- a/apps/sim/app/api/tools/sms/send/route.ts +++ b/apps/sim/app/api/tools/sms/send/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { env } from '@/lib/core/config/env' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { type SMSOptions, sendSMS } from '@/lib/messaging/sms/service' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/smtp/send/route.ts b/apps/sim/app/api/tools/smtp/send/route.ts index d20b27b328..75008909e3 100644 --- a/apps/sim/app/api/tools/smtp/send/route.ts +++ b/apps/sim/app/api/tools/smtp/send/route.ts @@ -1,9 +1,9 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import nodemailer from 'nodemailer' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/sqs/send/route.ts b/apps/sim/app/api/tools/sqs/send/route.ts index 402f5ca53c..c738adf9e7 100644 --- a/apps/sim/app/api/tools/sqs/send/route.ts +++ b/apps/sim/app/api/tools/sqs/send/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSqsClient, sendMessage } from '../utils' const logger = createLogger('SQSSendMessageAPI') diff --git a/apps/sim/app/api/tools/ssh/check-command-exists/route.ts b/apps/sim/app/api/tools/ssh/check-command-exists/route.ts index abed8abbfd..57fc1b087e 100644 --- a/apps/sim/app/api/tools/ssh/check-command-exists/route.ts +++ b/apps/sim/app/api/tools/ssh/check-command-exists/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHCheckCommandExistsAPI') diff --git a/apps/sim/app/api/tools/ssh/check-file-exists/route.ts b/apps/sim/app/api/tools/ssh/check-file-exists/route.ts index 0830488db1..445ab3bd39 100644 --- a/apps/sim/app/api/tools/ssh/check-file-exists/route.ts +++ b/apps/sim/app/api/tools/ssh/check-file-exists/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper, Stats } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, getFileType, diff --git a/apps/sim/app/api/tools/ssh/create-directory/route.ts b/apps/sim/app/api/tools/ssh/create-directory/route.ts index 06f7c412ff..43c0d27218 100644 --- a/apps/sim/app/api/tools/ssh/create-directory/route.ts +++ b/apps/sim/app/api/tools/ssh/create-directory/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/delete-file/route.ts b/apps/sim/app/api/tools/ssh/delete-file/route.ts index a1cb694fa2..3961fe60c2 100644 --- a/apps/sim/app/api/tools/ssh/delete-file/route.ts +++ b/apps/sim/app/api/tools/ssh/delete-file/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/download-file/route.ts b/apps/sim/app/api/tools/ssh/download-file/route.ts index 03f5b2cfab..3693f22edb 100644 --- a/apps/sim/app/api/tools/ssh/download-file/route.ts +++ b/apps/sim/app/api/tools/ssh/download-file/route.ts @@ -1,9 +1,9 @@ import { randomUUID } from 'crypto' import path from 'path' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHDownloadFileAPI') diff --git a/apps/sim/app/api/tools/ssh/execute-command/route.ts b/apps/sim/app/api/tools/ssh/execute-command/route.ts index c553b3554e..1d53d38535 100644 --- a/apps/sim/app/api/tools/ssh/execute-command/route.ts +++ b/apps/sim/app/api/tools/ssh/execute-command/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, executeSSHCommand, sanitizeCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHExecuteCommandAPI') diff --git a/apps/sim/app/api/tools/ssh/execute-script/route.ts b/apps/sim/app/api/tools/ssh/execute-script/route.ts index 0e7e44abfe..956318495f 100644 --- a/apps/sim/app/api/tools/ssh/execute-script/route.ts +++ b/apps/sim/app/api/tools/ssh/execute-script/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHExecuteScriptAPI') diff --git a/apps/sim/app/api/tools/ssh/get-system-info/route.ts b/apps/sim/app/api/tools/ssh/get-system-info/route.ts index 9f88415e55..9925013478 100644 --- a/apps/sim/app/api/tools/ssh/get-system-info/route.ts +++ b/apps/sim/app/api/tools/ssh/get-system-info/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, executeSSHCommand } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHGetSystemInfoAPI') diff --git a/apps/sim/app/api/tools/ssh/list-directory/route.ts b/apps/sim/app/api/tools/ssh/list-directory/route.ts index dbcfeb78eb..30f8f5d236 100644 --- a/apps/sim/app/api/tools/ssh/list-directory/route.ts +++ b/apps/sim/app/api/tools/ssh/list-directory/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, FileEntry, SFTPWrapper } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, getFileType, diff --git a/apps/sim/app/api/tools/ssh/move-rename/route.ts b/apps/sim/app/api/tools/ssh/move-rename/route.ts index d028399668..d1387026dd 100644 --- a/apps/sim/app/api/tools/ssh/move-rename/route.ts +++ b/apps/sim/app/api/tools/ssh/move-rename/route.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, escapeShellArg, diff --git a/apps/sim/app/api/tools/ssh/read-file-content/route.ts b/apps/sim/app/api/tools/ssh/read-file-content/route.ts index 3c8cb25dd4..c44390bfc0 100644 --- a/apps/sim/app/api/tools/ssh/read-file-content/route.ts +++ b/apps/sim/app/api/tools/ssh/read-file-content/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHReadFileContentAPI') diff --git a/apps/sim/app/api/tools/ssh/upload-file/route.ts b/apps/sim/app/api/tools/ssh/upload-file/route.ts index 7166856cb4..0f736a417d 100644 --- a/apps/sim/app/api/tools/ssh/upload-file/route.ts +++ b/apps/sim/app/api/tools/ssh/upload-file/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHUploadFileAPI') diff --git a/apps/sim/app/api/tools/ssh/utils.ts b/apps/sim/app/api/tools/ssh/utils.ts index b2d2a581c0..126849ba90 100644 --- a/apps/sim/app/api/tools/ssh/utils.ts +++ b/apps/sim/app/api/tools/ssh/utils.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { type Attributes, Client, type ConnectConfig } from 'ssh2' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SSHUtils') diff --git a/apps/sim/app/api/tools/ssh/write-file-content/route.ts b/apps/sim/app/api/tools/ssh/write-file-content/route.ts index 5ba7274015..77c075abb4 100644 --- a/apps/sim/app/api/tools/ssh/write-file-content/route.ts +++ b/apps/sim/app/api/tools/ssh/write-file-content/route.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto' +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import type { Client, SFTPWrapper } from 'ssh2' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' const logger = createLogger('SSHWriteFileContentAPI') diff --git a/apps/sim/app/api/tools/stagehand/agent/route.ts b/apps/sim/app/api/tools/stagehand/agent/route.ts index d1aeeb0950..ee5ffe6e25 100644 --- a/apps/sim/app/api/tools/stagehand/agent/route.ts +++ b/apps/sim/app/api/tools/stagehand/agent/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { env } from '@/lib/core/config/env' import { isSensitiveKey, REDACTED_MARKER } from '@/lib/core/security/redaction' -import { createLogger } from '@/lib/logs/console/logger' import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils' const logger = createLogger('StagehandAgentAPI') diff --git a/apps/sim/app/api/tools/stagehand/extract/route.ts b/apps/sim/app/api/tools/stagehand/extract/route.ts index 7da282815d..18f3e408b3 100644 --- a/apps/sim/app/api/tools/stagehand/extract/route.ts +++ b/apps/sim/app/api/tools/stagehand/extract/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils' const logger = createLogger('StagehandExtractAPI') diff --git a/apps/sim/app/api/tools/stagehand/utils.ts b/apps/sim/app/api/tools/stagehand/utils.ts index 4cf3e0c148..1e61f2971f 100644 --- a/apps/sim/app/api/tools/stagehand/utils.ts +++ b/apps/sim/app/api/tools/stagehand/utils.ts @@ -1,5 +1,5 @@ +import type { Logger } from '@sim/logger' import { z } from 'zod' -import type { Logger } from '@/lib/logs/console/logger' function jsonSchemaToZod(logger: Logger, jsonSchema: Record): z.ZodTypeAny { if (!jsonSchema) { diff --git a/apps/sim/app/api/tools/telegram/send-document/route.ts b/apps/sim/app/api/tools/telegram/send-document/route.ts index 968b110785..d0d656e0b9 100644 --- a/apps/sim/app/api/tools/telegram/send-document/route.ts +++ b/apps/sim/app/api/tools/telegram/send-document/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { convertMarkdownToHTML } from '@/tools/telegram/utils' diff --git a/apps/sim/app/api/tools/thinking/route.ts b/apps/sim/app/api/tools/thinking/route.ts index 97e41ff3eb..8b397db5ee 100644 --- a/apps/sim/app/api/tools/thinking/route.ts +++ b/apps/sim/app/api/tools/thinking/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import type { ThinkingToolParams, ThinkingToolResponse } from '@/tools/thinking/types' const logger = createLogger('ThinkingToolAPI') diff --git a/apps/sim/app/api/tools/vision/analyze/route.ts b/apps/sim/app/api/tools/vision/analyze/route.ts index ded0b5dc85..58c3515ad0 100644 --- a/apps/sim/app/api/tools/vision/analyze/route.ts +++ b/apps/sim/app/api/tools/vision/analyze/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' diff --git a/apps/sim/app/api/tools/wealthbox/item/route.ts b/apps/sim/app/api/tools/wealthbox/item/route.ts index 12c423fcd5..b618470e6f 100644 --- a/apps/sim/app/api/tools/wealthbox/item/route.ts +++ b/apps/sim/app/api/tools/wealthbox/item/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/wealthbox/items/route.ts b/apps/sim/app/api/tools/wealthbox/items/route.ts index dd041f5d95..a07ff62c41 100644 --- a/apps/sim/app/api/tools/wealthbox/items/route.ts +++ b/apps/sim/app/api/tools/wealthbox/items/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' diff --git a/apps/sim/app/api/tools/webflow/collections/route.ts b/apps/sim/app/api/tools/webflow/collections/route.ts index 31ec540615..8562da8ac1 100644 --- a/apps/sim/app/api/tools/webflow/collections/route.ts +++ b/apps/sim/app/api/tools/webflow/collections/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' const logger = createLogger('WebflowCollectionsAPI') diff --git a/apps/sim/app/api/tools/webflow/items/route.ts b/apps/sim/app/api/tools/webflow/items/route.ts index 95acc644d7..b2c5512167 100644 --- a/apps/sim/app/api/tools/webflow/items/route.ts +++ b/apps/sim/app/api/tools/webflow/items/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' const logger = createLogger('WebflowItemsAPI') diff --git a/apps/sim/app/api/tools/webflow/sites/route.ts b/apps/sim/app/api/tools/webflow/sites/route.ts index f5fd93ee2a..47959f4c93 100644 --- a/apps/sim/app/api/tools/webflow/sites/route.ts +++ b/apps/sim/app/api/tools/webflow/sites/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' const logger = createLogger('WebflowSitesAPI') diff --git a/apps/sim/app/api/tools/wordpress/upload/route.ts b/apps/sim/app/api/tools/wordpress/upload/route.ts index 56c0beaf3f..7f0434bc1f 100644 --- a/apps/sim/app/api/tools/wordpress/upload/route.ts +++ b/apps/sim/app/api/tools/wordpress/upload/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getFileExtension, getMimeTypeFromExtension, diff --git a/apps/sim/app/api/usage/route.ts b/apps/sim/app/api/usage/route.ts index 4ca818e787..f55f57c496 100644 --- a/apps/sim/app/api/usage/route.ts +++ b/apps/sim/app/api/usage/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -6,7 +7,6 @@ import { getOrganizationBillingData, isOrganizationOwnerOrAdmin, } from '@/lib/billing/core/organization' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UnifiedUsageAPI') diff --git a/apps/sim/app/api/user/super-user/route.ts b/apps/sim/app/api/user/super-user/route.ts index cc39943434..28c8b9733e 100644 --- a/apps/sim/app/api/user/super-user/route.ts +++ b/apps/sim/app/api/user/super-user/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SuperUserAPI') diff --git a/apps/sim/app/api/users/me/api-keys/[id]/route.ts b/apps/sim/app/api/users/me/api-keys/[id]/route.ts index fb5ea90e8d..56be3ce7bb 100644 --- a/apps/sim/app/api/users/me/api-keys/[id]/route.ts +++ b/apps/sim/app/api/users/me/api-keys/[id]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { apiKey } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ApiKeyAPI') diff --git a/apps/sim/app/api/users/me/api-keys/route.ts b/apps/sim/app/api/users/me/api-keys/route.ts index ca4e78d577..252011ec95 100644 --- a/apps/sim/app/api/users/me/api-keys/route.ts +++ b/apps/sim/app/api/users/me/api-keys/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { apiKey } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { createApiKey, getApiKeyDisplayFormat } from '@/lib/api-key/auth' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ApiKeysAPI') diff --git a/apps/sim/app/api/users/me/profile/route.ts b/apps/sim/app/api/users/me/profile/route.ts index 7f6ebe1489..1b627dbac1 100644 --- a/apps/sim/app/api/users/me/profile/route.ts +++ b/apps/sim/app/api/users/me/profile/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UpdateUserProfileAPI') diff --git a/apps/sim/app/api/users/me/settings/route.ts b/apps/sim/app/api/users/me/settings/route.ts index 6fdf0986c5..6f6094558f 100644 --- a/apps/sim/app/api/users/me/settings/route.ts +++ b/apps/sim/app/api/users/me/settings/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { settings } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UserSettingsAPI') diff --git a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts index 8dc42c36e0..30c7799995 100644 --- a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts +++ b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import type { EmailType } from '@/lib/messaging/email/mailer' import { getEmailPreferences, diff --git a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts index a20f600b7d..c00777ce34 100644 --- a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts +++ b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { member, organization, subscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SubscriptionTransferAPI') diff --git a/apps/sim/app/api/users/me/usage-limits/route.ts b/apps/sim/app/api/users/me/usage-limits/route.ts index 9960a05294..26db257efc 100644 --- a/apps/sim/app/api/users/me/usage-limits/route.ts +++ b/apps/sim/app/api/users/me/usage-limits/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { checkServerSideUsageLimits } from '@/lib/billing' @@ -5,7 +6,6 @@ import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { getEffectiveCurrentPeriodCost } from '@/lib/billing/core/usage' import { getUserStorageLimit, getUserStorageUsage } from '@/lib/billing/storage' import { RateLimiter } from '@/lib/core/rate-limiter' -import { createLogger } from '@/lib/logs/console/logger' import { createErrorResponse } from '@/app/api/workflows/utils' const logger = createLogger('UsageLimitsAPI') diff --git a/apps/sim/app/api/users/me/usage-logs/route.ts b/apps/sim/app/api/users/me/usage-logs/route.ts index b4751fbdcd..3c4f1229fe 100644 --- a/apps/sim/app/api/users/me/usage-logs/route.ts +++ b/apps/sim/app/api/users/me/usage-logs/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { getUserUsageLogs, type UsageLogSource } from '@/lib/billing/core/usage-log' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UsageLogsAPI') diff --git a/apps/sim/app/api/v1/admin/auth.ts b/apps/sim/app/api/v1/admin/auth.ts index 642968b996..5e04bcc1d9 100644 --- a/apps/sim/app/api/v1/admin/auth.ts +++ b/apps/sim/app/api/v1/admin/auth.ts @@ -9,9 +9,9 @@ */ import { createHash, timingSafeEqual } from 'crypto' +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('AdminAuth') diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts index 70d937604f..952b437144 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts @@ -17,9 +17,9 @@ import { db } from '@sim/db' import { organization } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { getOrganizationBillingData } from '@/lib/billing/core/organization' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts index 58048cf685..2496c363c6 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts @@ -27,9 +27,9 @@ import { db } from '@sim/db' import { member, organization, user, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts index a3c07e02ed..797831b887 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts @@ -30,10 +30,10 @@ import { db } from '@sim/db' import { member, organization, user, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count, eq } from 'drizzle-orm' import { addUserToOrganization } from '@/lib/billing/organizations/membership' import { requireStripeClient } from '@/lib/billing/stripe-client' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/route.ts index ef5c9e9663..3d0373014e 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/route.ts @@ -18,8 +18,8 @@ import { db } from '@sim/db' import { member, organization, subscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, count, eq } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts index 0cfe0c8d94..86e156a445 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts @@ -6,8 +6,8 @@ * Response: AdminSingleResponse */ +import { createLogger } from '@sim/logger' import { getOrganizationSeatAnalytics } from '@/lib/billing/validation/seat-management' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, diff --git a/apps/sim/app/api/v1/admin/organizations/route.ts b/apps/sim/app/api/v1/admin/organizations/route.ts index a05fafc5f1..f19f822467 100644 --- a/apps/sim/app/api/v1/admin/organizations/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/route.ts @@ -12,8 +12,8 @@ import { db } from '@sim/db' import { organization } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/subscriptions/[id]/route.ts b/apps/sim/app/api/v1/admin/subscriptions/[id]/route.ts index dac1dde893..50ba40f333 100644 --- a/apps/sim/app/api/v1/admin/subscriptions/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/subscriptions/[id]/route.ts @@ -25,9 +25,9 @@ import { db } from '@sim/db' import { subscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { requireStripeClient } from '@/lib/billing/stripe-client' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/subscriptions/route.ts b/apps/sim/app/api/v1/admin/subscriptions/route.ts index be73517216..146d5c307b 100644 --- a/apps/sim/app/api/v1/admin/subscriptions/route.ts +++ b/apps/sim/app/api/v1/admin/subscriptions/route.ts @@ -14,8 +14,8 @@ import { db } from '@sim/db' import { subscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, count, eq, type SQL } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts index ef2535adc4..e5681df62a 100644 --- a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts @@ -20,10 +20,10 @@ import { db } from '@sim/db' import { member, organization, subscription, user, userStats } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, or } from 'drizzle-orm' import { nanoid } from 'nanoid' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { badRequestResponse, diff --git a/apps/sim/app/api/v1/admin/users/[id]/route.ts b/apps/sim/app/api/v1/admin/users/[id]/route.ts index e1b52c7e9b..3700a427b1 100644 --- a/apps/sim/app/api/v1/admin/users/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/users/[id]/route.ts @@ -8,8 +8,8 @@ import { db } from '@sim/db' import { user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, diff --git a/apps/sim/app/api/v1/admin/users/route.ts b/apps/sim/app/api/v1/admin/users/route.ts index 698d75808c..a8400bced6 100644 --- a/apps/sim/app/api/v1/admin/users/route.ts +++ b/apps/sim/app/api/v1/admin/users/route.ts @@ -12,8 +12,8 @@ import { db } from '@sim/db' import { user } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/workflows/[id]/export/route.ts b/apps/sim/app/api/v1/admin/workflows/[id]/export/route.ts index 7aa6ad503e..3570cc9f31 100644 --- a/apps/sim/app/api/v1/admin/workflows/[id]/export/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/[id]/export/route.ts @@ -8,8 +8,8 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { diff --git a/apps/sim/app/api/v1/admin/workflows/[id]/route.ts b/apps/sim/app/api/v1/admin/workflows/[id]/route.ts index 8aae98af42..ca596d6afd 100644 --- a/apps/sim/app/api/v1/admin/workflows/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/[id]/route.ts @@ -14,9 +14,9 @@ import { db } from '@sim/db' import { workflow, workflowBlocks, workflowEdges, workflowSchedule } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, diff --git a/apps/sim/app/api/v1/admin/workflows/import/route.ts b/apps/sim/app/api/v1/admin/workflows/import/route.ts index 9dc00e5d0e..db83f52d07 100644 --- a/apps/sim/app/api/v1/admin/workflows/import/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/import/route.ts @@ -16,9 +16,9 @@ import { db } from '@sim/db' import { workflow, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { diff --git a/apps/sim/app/api/v1/admin/workflows/route.ts b/apps/sim/app/api/v1/admin/workflows/route.ts index 3c190330a2..5344a5db63 100644 --- a/apps/sim/app/api/v1/admin/workflows/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/route.ts @@ -12,8 +12,8 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/export/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/export/route.ts index a943cfa7a3..f7e60502ad 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/export/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/export/route.ts @@ -13,9 +13,9 @@ import { db } from '@sim/db' import { workflow, workflowFolder, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { exportWorkspaceToZip } from '@/lib/workflows/operations/import-export' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/folders/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/folders/route.ts index a484643d1a..37cdc2b964 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/folders/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/folders/route.ts @@ -12,8 +12,8 @@ import { db } from '@sim/db' import { workflowFolder, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count, eq } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse, notFoundResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts index 11989448ec..fa569b7f24 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts @@ -25,9 +25,9 @@ import { db } from '@sim/db' import { workflow, workflowFolder, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { extractWorkflowName, extractWorkflowsFromZip, diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/route.ts index c9dd07a237..ee34556fc6 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/route.ts @@ -8,8 +8,8 @@ import { db } from '@sim/db' import { workflow, workflowFolder, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count, eq } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts index 867f5f2a7b..ea1ab87fc5 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts @@ -24,9 +24,9 @@ import { workflowSchedule, workspace, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count, eq, inArray } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse, notFoundResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/admin/workspaces/route.ts b/apps/sim/app/api/v1/admin/workspaces/route.ts index 1f3fe3e197..0724770ced 100644 --- a/apps/sim/app/api/v1/admin/workspaces/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/route.ts @@ -12,8 +12,8 @@ import { db } from '@sim/db' import { workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { count } from 'drizzle-orm' -import { createLogger } from '@/lib/logs/console/logger' import { withAdminAuth } from '@/app/api/v1/admin/middleware' import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses' import { diff --git a/apps/sim/app/api/v1/auth.ts b/apps/sim/app/api/v1/auth.ts index 30bf8d8e51..ce288dd676 100644 --- a/apps/sim/app/api/v1/auth.ts +++ b/apps/sim/app/api/v1/auth.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service' import { ANONYMOUS_USER_ID } from '@/lib/auth/constants' import { isAuthDisabled } from '@/lib/core/config/feature-flags' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('V1Auth') diff --git a/apps/sim/app/api/v1/logs/[id]/route.ts b/apps/sim/app/api/v1/logs/[id]/route.ts index aa53fb496c..b1d8f89ff3 100644 --- a/apps/sim/app/api/v1/logs/[id]/route.ts +++ b/apps/sim/app/api/v1/logs/[id]/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts index a68cd0f313..5c2967ef73 100644 --- a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts +++ b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts @@ -5,9 +5,9 @@ import { workflowExecutionLogs, workflowExecutionSnapshots, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index 8357175947..83a7b62192 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { createLogger } from '@/lib/logs/console/logger' import { buildLogFilters, getOrderBy } from '@/app/api/v1/logs/filters' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' diff --git a/apps/sim/app/api/v1/middleware.ts b/apps/sim/app/api/v1/middleware.ts index ae00e5eeac..4f0eac4ad9 100644 --- a/apps/sim/app/api/v1/middleware.ts +++ b/apps/sim/app/api/v1/middleware.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { RateLimiter } from '@/lib/core/rate-limiter' -import { createLogger } from '@/lib/logs/console/logger' import { authenticateV1Request } from '@/app/api/v1/auth' const logger = createLogger('V1Middleware') diff --git a/apps/sim/app/api/wand/route.ts b/apps/sim/app/api/wand/route.ts index bb2a277768..3a14e74001 100644 --- a/apps/sim/app/api/wand/route.ts +++ b/apps/sim/app/api/wand/route.ts @@ -1,15 +1,16 @@ import { db } from '@sim/db' import { userStats, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import OpenAI, { AzureOpenAI } from 'openai' +import { getBYOKKey } from '@/lib/api-key/byok' import { getSession } from '@/lib/auth' import { logModelUsage } from '@/lib/billing/core/usage-log' import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' import { env } from '@/lib/core/config/env' import { getCostMultiplier, isBillingEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' import { getModelPricing } from '@/providers/utils' @@ -75,7 +76,8 @@ async function updateUserStatsForWand( completion_tokens?: number total_tokens?: number }, - requestId: string + requestId: string, + isBYOK = false ): Promise { if (!isBillingEnabled) { logger.debug(`[${requestId}] Billing is disabled, skipping wand usage cost update`) @@ -93,21 +95,24 @@ async function updateUserStatsForWand( const completionTokens = usage.completion_tokens || 0 const modelName = useWandAzure ? wandModelName : 'gpt-4o' - const pricing = getModelPricing(modelName) - - const costMultiplier = getCostMultiplier() - let modelCost = 0 + let costToStore = 0 + + if (!isBYOK) { + 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 + } - 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 + costToStore = modelCost * costMultiplier } - const costToStore = modelCost * costMultiplier - await db .update(userStats) .set({ @@ -122,6 +127,7 @@ async function updateUserStatsForWand( userId, tokensUsed: totalTokens, costAdded: costToStore, + isBYOK, }) await logModelUsage({ @@ -149,14 +155,6 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }) } - if (!client) { - logger.error(`[${requestId}] AI client not initialized. Missing API key.`) - return NextResponse.json( - { success: false, error: 'Wand generation service is not configured.' }, - { status: 503 } - ) - } - try { const body = (await req.json()) as RequestBody @@ -170,6 +168,7 @@ export async function POST(req: NextRequest) { ) } + let workspaceId: string | null = null if (workflowId) { const [workflowRecord] = await db .select({ workspaceId: workflow.workspaceId, userId: workflow.userId }) @@ -182,6 +181,8 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: false, error: 'Workflow not found' }, { status: 404 }) } + workspaceId = workflowRecord.workspaceId + if (workflowRecord.workspaceId) { const permission = await verifyWorkspaceMembership( session.user.id, @@ -199,6 +200,28 @@ export async function POST(req: NextRequest) { } } + let isBYOK = false + let activeClient = client + let byokApiKey: string | null = null + + if (workspaceId && !useWandAzure) { + const byokResult = await getBYOKKey(workspaceId, 'openai') + if (byokResult) { + isBYOK = true + byokApiKey = byokResult.apiKey + activeClient = new OpenAI({ apiKey: byokResult.apiKey }) + logger.info(`[${requestId}] Using BYOK OpenAI key for wand generation`) + } + } + + if (!activeClient) { + logger.error(`[${requestId}] AI client not initialized. Missing API key.`) + return NextResponse.json( + { success: false, error: 'Wand generation service is not configured.' }, + { status: 503 } + ) + } + const finalSystemPrompt = systemPrompt || 'You are a helpful AI assistant. Generate content exactly as requested by the user.' @@ -241,7 +264,7 @@ export async function POST(req: NextRequest) { if (useWandAzure) { headers['api-key'] = azureApiKey! } else { - headers.Authorization = `Bearer ${openaiApiKey}` + headers.Authorization = `Bearer ${byokApiKey || openaiApiKey}` } logger.debug(`[${requestId}] Making streaming request to: ${apiUrl}`) @@ -310,7 +333,7 @@ export async function POST(req: NextRequest) { logger.info(`[${requestId}] Received [DONE] signal`) if (finalUsage) { - await updateUserStatsForWand(session.user.id, finalUsage, requestId) + await updateUserStatsForWand(session.user.id, finalUsage, requestId, isBYOK) } controller.enqueue( @@ -395,7 +418,7 @@ export async function POST(req: NextRequest) { } } - const completion = await client.chat.completions.create({ + const completion = await activeClient.chat.completions.create({ model: useWandAzure ? wandModelName : 'gpt-4o', messages: messages, temperature: 0.3, @@ -417,7 +440,7 @@ export async function POST(req: NextRequest) { logger.info(`[${requestId}] Wand generation successful`) if (completion.usage) { - await updateUserStatsForWand(session.user.id, completion.usage, requestId) + await updateUserStatsForWand(session.user.id, completion.usage, requestId, isBYOK) } return NextResponse.json({ success: true, content: generatedContent }) diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index 286472f25e..6f7ffc3a3d 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { webhook, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { validateInteger } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WebhookAPI') diff --git a/apps/sim/app/api/webhooks/[id]/test-url/route.ts b/apps/sim/app/api/webhooks/[id]/test-url/route.ts index 066c6b3cae..7b27b2280c 100644 --- a/apps/sim/app/api/webhooks/[id]/test-url/route.ts +++ b/apps/sim/app/api/webhooks/[id]/test-url/route.ts @@ -1,10 +1,10 @@ import { db, webhook, workflow } from '@sim/db' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { signTestWebhookToken } from '@/lib/webhooks/test-tokens' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts b/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts index 0b6be60936..a460803345 100644 --- a/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts +++ b/apps/sim/app/api/webhooks/cleanup/idempotency/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { cleanupExpiredIdempotencyKeys, getIdempotencyKeyStats } from '@/lib/core/idempotency' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('IdempotencyCleanupAPI') diff --git a/apps/sim/app/api/webhooks/poll/gmail/route.ts b/apps/sim/app/api/webhooks/poll/gmail/route.ts index 008561b603..7b8f6c250b 100644 --- a/apps/sim/app/api/webhooks/poll/gmail/route.ts +++ b/apps/sim/app/api/webhooks/poll/gmail/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { createLogger } from '@/lib/logs/console/logger' import { pollGmailWebhooks } from '@/lib/webhooks/gmail-polling-service' const logger = createLogger('GmailPollingAPI') diff --git a/apps/sim/app/api/webhooks/poll/outlook/route.ts b/apps/sim/app/api/webhooks/poll/outlook/route.ts index eccbfe7b34..c7266fa636 100644 --- a/apps/sim/app/api/webhooks/poll/outlook/route.ts +++ b/apps/sim/app/api/webhooks/poll/outlook/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { createLogger } from '@/lib/logs/console/logger' import { pollOutlookWebhooks } from '@/lib/webhooks/outlook-polling-service' const logger = createLogger('OutlookPollingAPI') diff --git a/apps/sim/app/api/webhooks/poll/rss/route.ts b/apps/sim/app/api/webhooks/poll/rss/route.ts index fabe2c4934..1f9201ee7d 100644 --- a/apps/sim/app/api/webhooks/poll/rss/route.ts +++ b/apps/sim/app/api/webhooks/poll/rss/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { acquireLock, releaseLock } from '@/lib/core/config/redis' -import { createLogger } from '@/lib/logs/console/logger' import { pollRssWebhooks } from '@/lib/webhooks/rss-polling-service' const logger = createLogger('RssPollingAPI') diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 3210615b1d..4c2d2735f4 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { webhook, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { getOAuthToken } from '@/app/api/auth/oauth/utils' diff --git a/apps/sim/app/api/webhooks/test/[id]/route.ts b/apps/sim/app/api/webhooks/test/[id]/route.ts index d66d69f407..46653c3bf7 100644 --- a/apps/sim/app/api/webhooks/test/[id]/route.ts +++ b/apps/sim/app/api/webhooks/test/[id]/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { checkWebhookPreprocessing, findWebhookAndWorkflow, diff --git a/apps/sim/app/api/webhooks/test/route.ts b/apps/sim/app/api/webhooks/test/route.ts index 021dc670bd..bf3aece243 100644 --- a/apps/sim/app/api/webhooks/test/route.ts +++ b/apps/sim/app/api/webhooks/test/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { webhook } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('WebhookTestAPI') diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts index 3ac1c87ae5..e736db3987 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts @@ -3,6 +3,8 @@ * * @vitest-environment node */ + +import { loggerMock } from '@sim/testing' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createMockRequest, @@ -176,6 +178,8 @@ vi.mock('drizzle-orm/postgres-js', () => ({ vi.mock('postgres', () => vi.fn().mockReturnValue({})) +vi.mock('@sim/logger', () => loggerMock) + process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test' import { POST } from '@/app/api/webhooks/trigger/[path]/route' @@ -257,9 +261,6 @@ describe('Webhook Trigger API Route', () => { expect(data.message).toBe('Webhook processed') }) - /** - * Test generic webhook with Bearer token authentication - */ it('should authenticate with Bearer token when no custom header is configured', async () => { globalMockData.webhooks.push({ id: 'generic-webhook-id', @@ -489,7 +490,7 @@ describe('Webhook Trigger API Route', () => { const headers = { 'Content-Type': 'application/json', - Authorization: 'Bearer exclusive-token', // Correct token but wrong header type + Authorization: 'Bearer exclusive-token', } const req = createMockRequest('POST', { event: 'exclusivity.test' }, headers) const params = Promise.resolve({ path: 'test-path' }) @@ -517,7 +518,7 @@ describe('Webhook Trigger API Route', () => { const headers = { 'Content-Type': 'application/json', - 'X-Wrong-Header': 'correct-token', // Correct token but wrong header name + 'X-Wrong-Header': 'correct-token', } const req = createMockRequest('POST', { event: 'wrong.header.name.test' }, headers) const params = Promise.resolve({ path: 'test-path' }) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.ts index b7ec7bafbe..549ce6a78d 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.ts @@ -1,6 +1,6 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { checkWebhookPreprocessing, findWebhookAndWorkflow, diff --git a/apps/sim/app/api/workflows/[id]/autolayout/route.ts b/apps/sim/app/api/workflows/[id]/autolayout/route.ts index a08c82fb72..06e2c33133 100644 --- a/apps/sim/app/api/workflows/[id]/autolayout/route.ts +++ b/apps/sim/app/api/workflows/[id]/autolayout/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { applyAutoLayout } from '@/lib/workflows/autolayout' import { DEFAULT_HORIZONTAL_SPACING, diff --git a/apps/sim/app/api/workflows/[id]/chat/status/route.ts b/apps/sim/app/api/workflows/[id]/chat/status/route.ts index 21b3758a7b..f7733e1407 100644 --- a/apps/sim/app/api/workflows/[id]/chat/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/chat/status/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' const logger = createLogger('ChatStatusAPI') diff --git a/apps/sim/app/api/workflows/[id]/deploy/route.ts b/apps/sim/app/api/workflows/[id]/deploy/route.ts index cb898ff5d3..c54124f47d 100644 --- a/apps/sim/app/api/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/workflows/[id]/deploy/route.ts @@ -1,8 +1,8 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { deployWorkflow, loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { createSchedulesForDeploy, @@ -60,13 +60,20 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const { loadWorkflowFromNormalizedTables } = await import('@/lib/workflows/persistence/utils') const normalizedData = await loadWorkflowFromNormalizedTables(id) if (normalizedData) { + const [workflowRecord] = await db + .select({ variables: workflow.variables }) + .from(workflow) + .where(eq(workflow.id, id)) + .limit(1) + const currentState = { blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, parallels: normalizedData.parallels, + variables: workflowRecord?.variables || {}, } - const { hasWorkflowChanged } = await import('@/lib/workflows/utils') + const { hasWorkflowChanged } = await import('@/lib/workflows/comparison') needsRedeployment = hasWorkflowChanged(currentState as any, active.state as any) } } diff --git a/apps/sim/app/api/workflows/[id]/deployed/route.ts b/apps/sim/app/api/workflows/[id]/deployed/route.ts index 735b481e62..e939fc0f09 100644 --- a/apps/sim/app/api/workflows/[id]/deployed/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployed/route.ts @@ -1,9 +1,9 @@ import { db, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import type { NextRequest, NextResponse } from 'next/server' import { verifyInternalToken } from '@/lib/auth/internal' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { validateWorkflowPermissions } from '@/lib/workflows/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts index 4961ec65d0..1ef4761e68 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts @@ -1,8 +1,8 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { validateWorkflowPermissions } from '@/lib/workflows/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts index a3153290ca..5b33e6c146 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts @@ -1,9 +1,9 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { env } from '@/lib/core/config/env' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' import { validateWorkflowPermissions } from '@/lib/workflows/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts index 3206798d70..20642c90ee 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts @@ -1,9 +1,9 @@ import { db, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { validateWorkflowPermissions } from '@/lib/workflows/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/deployments/route.ts b/apps/sim/app/api/workflows/[id]/deployments/route.ts index a74c015ffb..80ee376aa4 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/route.ts @@ -1,8 +1,8 @@ import { db, user, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { desc, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { validateWorkflowPermissions } from '@/lib/workflows/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/duplicate/route.ts b/apps/sim/app/api/workflows/[id]/duplicate/route.ts index 8e1bfe6497..41ce249d0c 100644 --- a/apps/sim/app/api/workflows/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workflows/[id]/duplicate/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' const logger = createLogger('WorkflowDuplicateAPI') diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index dd70158d38..5d1a7d7a02 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { tasks } from '@trigger.dev/sdk' import { type NextRequest, NextResponse } from 'next/server' import { validate as uuidValidate, v4 as uuidv4 } from 'uuid' @@ -7,9 +8,9 @@ import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { getBaseUrl } from '@/lib/core/utils/urls' +import { markExecutionCancelled } from '@/lib/execution/cancellation' import { processInputFileFields } from '@/lib/execution/files' import { preprocessExecution } from '@/lib/execution/preprocessing' -import { createLogger } from '@/lib/logs/console/logger' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { ALL_TRIGGER_TYPES } from '@/lib/logs/types' import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core' @@ -317,6 +318,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: loops: Record parallels: Record deploymentVersionId?: string + variables?: Record } | null = null let processedInput = input @@ -326,6 +328,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: : await loadDeployedWorkflowState(workflowId) if (workflowData) { + const deployedVariables = + !shouldUseDraftState && 'variables' in workflowData + ? (workflowData as any).variables + : undefined + cachedWorkflowData = { blocks: workflowData.blocks, edges: workflowData.edges, @@ -335,6 +342,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: !shouldUseDraftState && 'deploymentVersionId' in workflowData ? (workflowData.deploymentVersionId as string) : undefined, + variables: deployedVariables, } const serializedWorkflow = new Serializer().serializeWorkflow( @@ -404,11 +412,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowStateOverride: effectiveWorkflowStateOverride, } + const executionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {} + const snapshot = new ExecutionSnapshot( metadata, workflow, processedInput, - workflow.variables || {}, + executionVariables, selectedOutputs ) @@ -470,6 +480,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: selectedOutputs, cachedWorkflowData?.blocks || {} ) + const streamVariables = cachedWorkflowData?.variables ?? (workflow as any).variables + const stream = await createStreamingResponse({ requestId, workflow: { @@ -477,7 +489,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: userId: actorUserId, workspaceId, isDeployed: workflow.isDeployed, - variables: (workflow as any).variables, + variables: streamVariables, }, input: processedInput, executingUserId: actorUserId, @@ -496,7 +508,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: } const encoder = new TextEncoder() - let executorInstance: any = null + const abortController = new AbortController() let isStreamClosed = false const stream = new ReadableStream({ @@ -674,11 +686,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowStateOverride: effectiveWorkflowStateOverride, } + const sseExecutionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {} + const snapshot = new ExecutionSnapshot( metadata, workflow, processedInput, - workflow.variables || {}, + sseExecutionVariables, selectedOutputs ) @@ -688,11 +702,9 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: onBlockStart, onBlockComplete, onStream, - onExecutorCreated: (executor) => { - executorInstance = executor - }, }, loggingSession, + abortSignal: abortController.signal, }) if (result.status === 'paused') { @@ -769,11 +781,9 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: }, cancel() { isStreamClosed = true - logger.info(`[${requestId}] Client aborted SSE stream, cancelling executor`) - - if (executorInstance && typeof executorInstance.cancel === 'function') { - executorInstance.cancel() - } + logger.info(`[${requestId}] Client aborted SSE stream, signalling cancellation`) + abortController.abort() + markExecutionCancelled(executionId).catch(() => {}) }, }) diff --git a/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.ts b/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.ts new file mode 100644 index 0000000000..2544bb342c --- /dev/null +++ b/apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.ts @@ -0,0 +1,47 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { checkHybridAuth } from '@/lib/auth/hybrid' +import { markExecutionCancelled } from '@/lib/execution/cancellation' + +const logger = createLogger('CancelExecutionAPI') + +export const runtime = 'nodejs' +export const dynamic = 'force-dynamic' + +export async function POST( + req: NextRequest, + { params }: { params: Promise<{ id: string; executionId: string }> } +) { + const { id: workflowId, executionId } = await params + + try { + const auth = await checkHybridAuth(req, { requireWorkflowId: false }) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + logger.info('Cancel execution requested', { workflowId, executionId, userId: auth.userId }) + + const marked = await markExecutionCancelled(executionId) + + if (marked) { + logger.info('Execution marked as cancelled in Redis', { executionId }) + } else { + logger.info('Redis not available, cancellation will rely on connection close', { + executionId, + }) + } + + return NextResponse.json({ + success: true, + executionId, + redisAvailable: marked, + }) + } catch (error: any) { + logger.error('Failed to cancel execution', { workflowId, executionId, error: error.message }) + return NextResponse.json( + { error: error.message || 'Failed to cancel execution' }, + { status: 500 } + ) + } +} diff --git a/apps/sim/app/api/workflows/[id]/log/route.ts b/apps/sim/app/api/workflows/[id]/log/route.ts index dc41e04e62..744b4b545b 100644 --- a/apps/sim/app/api/workflows/[id]/log/route.ts +++ b/apps/sim/app/api/workflows/[id]/log/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' diff --git a/apps/sim/app/api/workflows/[id]/route.test.ts b/apps/sim/app/api/workflows/[id]/route.test.ts index abae661998..12ea444173 100644 --- a/apps/sim/app/api/workflows/[id]/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/route.test.ts @@ -20,7 +20,7 @@ vi.mock('@/lib/auth', () => ({ getSession: () => mockGetSession(), })) -vi.mock('@/lib/logs/console/logger', () => ({ +vi.mock('@sim/logger', () => ({ createLogger: vi.fn(() => ({ debug: vi.fn(), info: vi.fn(), diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index c4bab613d3..92a19d41c7 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { templates, webhook, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -8,7 +9,6 @@ import { getSession } from '@/lib/auth' import { verifyInternalToken } from '@/lib/auth/internal' import { env } from '@/lib/core/config/env' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { getWorkflowAccessContext, getWorkflowById } from '@/lib/workflows/utils' diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index ba68cb6966..43957ad95a 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { webhook, workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { extractAndPersistCustomTools } from '@/lib/workflows/persistence/custom-tools-persistence' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' import { sanitizeAgentToolsInBlocks } from '@/lib/workflows/sanitization/validation' diff --git a/apps/sim/app/api/workflows/[id]/status/route.ts b/apps/sim/app/api/workflows/[id]/status/route.ts index b25b21a6e8..b83dffed3a 100644 --- a/apps/sim/app/api/workflows/[id]/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/status/route.ts @@ -1,10 +1,10 @@ -import { db, workflowDeploymentVersion } from '@sim/db' +import { db, workflow, workflowDeploymentVersion } from '@sim/db' +import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' +import { hasWorkflowChanged } from '@/lib/workflows/comparison' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' -import { hasWorkflowChanged } from '@/lib/workflows/utils' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -22,17 +22,12 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return createErrorResponse(validation.error.message, validation.error.status) } - // Check if the workflow has meaningful changes that would require redeployment let needsRedeployment = false if (validation.workflow.isDeployed) { - // Get current state from normalized tables (same logic as deployment API) - // Load current state from normalized tables using centralized helper const normalizedData = await loadWorkflowFromNormalizedTables(id) if (!normalizedData) { - // Workflow exists but has no blocks in normalized tables (empty workflow or not migrated) - // This is valid state - return success with no redeployment needed return createSuccessResponse({ isDeployed: validation.workflow.isDeployed, deployedAt: validation.workflow.deployedAt, @@ -41,11 +36,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ }) } + const [workflowRecord] = await db + .select({ variables: workflow.variables }) + .from(workflow) + .where(eq(workflow.id, id)) + .limit(1) + const currentState = { blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, parallels: normalizedData.parallels, + variables: workflowRecord?.variables || {}, lastSaved: Date.now(), } @@ -69,6 +71,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return createSuccessResponse({ isDeployed: validation.workflow.isDeployed, deployedAt: validation.workflow.deployedAt, + isPublished: validation.workflow.isPublished, needsRedeployment, }) } catch (error) { diff --git a/apps/sim/app/api/workflows/[id]/variables/route.ts b/apps/sim/app/api/workflows/[id]/variables/route.ts index 88f80ce05d..ec7d5d486f 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getWorkflowAccessContext } from '@/lib/workflows/utils' import type { Variable } from '@/stores/panel/variables/types' diff --git a/apps/sim/app/api/workflows/middleware.ts b/apps/sim/app/api/workflows/middleware.ts index 883e02125c..d3cbfa3b6d 100644 --- a/apps/sim/app/api/workflows/middleware.ts +++ b/apps/sim/app/api/workflows/middleware.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import type { NextRequest } from 'next/server' import { type ApiKeyAuthResult, @@ -5,7 +6,6 @@ import { updateApiKeyLastUsed, } from '@/lib/api-key/service' import { env } from '@/lib/core/config/env' -import { createLogger } from '@/lib/logs/console/logger' import { getWorkflowById } from '@/lib/workflows/utils' const logger = createLogger('WorkflowMiddleware') diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 6b78495c55..4ff9d99acc 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workflows/utils.ts b/apps/sim/app/api/workflows/utils.ts index 348bedcb1e..a6646d3950 100644 --- a/apps/sim/app/api/workflows/utils.ts +++ b/apps/sim/app/api/workflows/utils.ts @@ -1,5 +1,5 @@ +import { createLogger } from '@sim/logger' import { NextResponse } from 'next/server' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkflowUtils') diff --git a/apps/sim/app/api/workflows/yaml/convert/route.ts b/apps/sim/app/api/workflows/yaml/convert/route.ts deleted file mode 100644 index 899585dfad..0000000000 --- a/apps/sim/app/api/workflows/yaml/convert/route.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { type NextRequest, NextResponse } from 'next/server' -import { simAgentClient } from '@/lib/copilot/client' -import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' -import { getAllBlocks } from '@/blocks/registry' -import type { BlockConfig } from '@/blocks/types' -import { resolveOutputType } from '@/blocks/utils' -import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils' - -const logger = createLogger('WorkflowYamlAPI') - -export async function POST(request: NextRequest) { - const requestId = generateRequestId() - - try { - logger.info(`[${requestId}] Converting workflow JSON to YAML`) - - const body = await request.json() - const { workflowState, subBlockValues, includeMetadata = false } = body - - if (!workflowState) { - return NextResponse.json( - { success: false, error: 'workflowState is required' }, - { status: 400 } - ) - } - - // Ensure loop blocks have their data populated with defaults - if (workflowState.blocks) { - Object.entries(workflowState.blocks).forEach(([blockId, block]: [string, any]) => { - if (block.type === 'loop') { - // Ensure data field exists - if (!block.data) { - block.data = {} - } - - // Apply defaults if not set - if (!block.data.loopType) { - block.data.loopType = 'for' - } - if (!block.data.count && block.data.count !== 0) { - block.data.count = 5 - } - if (!block.data.collection) { - block.data.collection = '' - } - if (!block.data.maxConcurrency) { - block.data.maxConcurrency = 1 - } - - logger.debug(`[${requestId}] Applied defaults to loop block ${blockId}:`, { - loopType: block.data.loopType, - count: block.data.count, - }) - } - }) - } - - // Gather block registry and utilities for sim-agent - const blocks = getAllBlocks() - const blockRegistry = blocks.reduce( - (acc, block) => { - const blockType = block.type - acc[blockType] = { - ...block, - id: blockType, - subBlocks: block.subBlocks || [], - outputs: block.outputs || {}, - } as any - return acc - }, - {} as Record - ) - - // Call sim-agent directly - const result = await simAgentClient.makeRequest('/api/workflow/to-yaml', { - body: { - workflowState, - subBlockValues, - blockRegistry, - utilities: { - generateLoopBlocks: generateLoopBlocks.toString(), - generateParallelBlocks: generateParallelBlocks.toString(), - resolveOutputType: resolveOutputType.toString(), - }, - }, - }) - - if (!result.success || !result.data?.yaml) { - return NextResponse.json( - { - success: false, - error: result.error || 'Failed to generate YAML', - }, - { status: result.status || 500 } - ) - } - - logger.info(`[${requestId}] Successfully generated YAML`, { - yamlLength: result.data.yaml.length, - }) - - return NextResponse.json({ - success: true, - yaml: result.data.yaml, - }) - } catch (error) { - logger.error(`[${requestId}] YAML generation failed`, error) - return NextResponse.json( - { - success: false, - error: `Failed to generate YAML: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - { status: 500 } - ) - } -} diff --git a/apps/sim/app/api/workflows/yaml/export/route.ts b/apps/sim/app/api/workflows/yaml/export/route.ts deleted file mode 100644 index 4292e82d4d..0000000000 --- a/apps/sim/app/api/workflows/yaml/export/route.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { db } from '@sim/db' -import { workflow } from '@sim/db/schema' -import { eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { getSession } from '@/lib/auth' -import { simAgentClient } from '@/lib/copilot/client' -import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' -import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' -import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' -import { getAllBlocks } from '@/blocks/registry' -import type { BlockConfig } from '@/blocks/types' -import { resolveOutputType } from '@/blocks/utils' -import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils' - -const logger = createLogger('WorkflowYamlExportAPI') - -export async function GET(request: NextRequest) { - const requestId = generateRequestId() - const url = new URL(request.url) - const workflowId = url.searchParams.get('workflowId') - - try { - logger.info(`[${requestId}] Exporting workflow YAML from database: ${workflowId}`) - - if (!workflowId) { - return NextResponse.json({ success: false, error: 'workflowId is required' }, { status: 400 }) - } - - // Get the session for authentication - const session = await getSession() - if (!session?.user?.id) { - logger.warn(`[${requestId}] Unauthorized access attempt for workflow ${workflowId}`) - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const userId = session.user.id - - // Fetch the workflow from database - const workflowData = await db - .select() - .from(workflow) - .where(eq(workflow.id, workflowId)) - .then((rows) => rows[0]) - - if (!workflowData) { - logger.warn(`[${requestId}] Workflow ${workflowId} not found`) - return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) - } - - // Check if user has access to this workflow - let hasAccess = false - - // Case 1: User owns the workflow - if (workflowData.userId === userId) { - hasAccess = true - } - - // Case 2: Workflow belongs to a workspace the user has permissions for - if (!hasAccess && workflowData.workspaceId) { - const userPermission = await getUserEntityPermissions( - userId, - 'workspace', - workflowData.workspaceId - ) - if (userPermission !== null) { - hasAccess = true - } - } - - if (!hasAccess) { - logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`) - return NextResponse.json({ error: 'Access denied' }, { status: 403 }) - } - - // Try to load from normalized tables first - logger.debug(`[${requestId}] Attempting to load workflow ${workflowId} from normalized tables`) - const normalizedData = await loadWorkflowFromNormalizedTables(workflowId) - - let workflowState: any - const subBlockValues: Record> = {} - - if (normalizedData) { - logger.debug(`[${requestId}] Found normalized data for workflow ${workflowId}:`, { - blocksCount: Object.keys(normalizedData.blocks).length, - edgesCount: normalizedData.edges.length, - }) - - // Use normalized table data - construct state from normalized tables - workflowState = { - deploymentStatuses: {}, - blocks: normalizedData.blocks, - edges: normalizedData.edges, - loops: normalizedData.loops, - parallels: normalizedData.parallels, - lastSaved: Date.now(), - isDeployed: workflowData.isDeployed || false, - deployedAt: workflowData.deployedAt, - } - - // Extract subblock values from the normalized blocks - Object.entries(normalizedData.blocks).forEach(([blockId, block]: [string, any]) => { - subBlockValues[blockId] = {} - if (block.subBlocks) { - Object.entries(block.subBlocks).forEach(([subBlockId, subBlock]: [string, any]) => { - if (subBlock && typeof subBlock === 'object' && 'value' in subBlock) { - subBlockValues[blockId][subBlockId] = subBlock.value - } - }) - } - }) - - logger.info(`[${requestId}] Loaded workflow ${workflowId} from normalized tables`) - } else { - return NextResponse.json( - { success: false, error: 'Workflow has no normalized data' }, - { status: 400 } - ) - } - - // Ensure loop blocks have their data populated with defaults - if (workflowState.blocks) { - Object.entries(workflowState.blocks).forEach(([blockId, block]: [string, any]) => { - if (block.type === 'loop') { - // Ensure data field exists - if (!block.data) { - block.data = {} - } - - // Apply defaults if not set - if (!block.data.loopType) { - block.data.loopType = 'for' - } - if (!block.data.count && block.data.count !== 0) { - block.data.count = 5 - } - if (!block.data.collection) { - block.data.collection = '' - } - if (!block.data.maxConcurrency) { - block.data.maxConcurrency = 1 - } - - logger.debug(`[${requestId}] Applied defaults to loop block ${blockId}:`, { - loopType: block.data.loopType, - count: block.data.count, - }) - } - }) - } - - // Gather block registry and utilities for sim-agent - const blocks = getAllBlocks() - const blockRegistry = blocks.reduce( - (acc, block) => { - const blockType = block.type - acc[blockType] = { - ...block, - id: blockType, - subBlocks: block.subBlocks || [], - outputs: block.outputs || {}, - } as any - return acc - }, - {} as Record - ) - - // Call sim-agent directly - const result = await simAgentClient.makeRequest('/api/workflow/to-yaml', { - body: { - workflowState, - subBlockValues, - blockRegistry, - utilities: { - generateLoopBlocks: generateLoopBlocks.toString(), - generateParallelBlocks: generateParallelBlocks.toString(), - resolveOutputType: resolveOutputType.toString(), - }, - }, - }) - - if (!result.success || !result.data?.yaml) { - return NextResponse.json( - { - success: false, - error: result.error || 'Failed to generate YAML', - }, - { status: result.status || 500 } - ) - } - - logger.info(`[${requestId}] Successfully generated YAML from database`, { - yamlLength: result.data.yaml.length, - }) - - return NextResponse.json({ - success: true, - yaml: result.data.yaml, - }) - } catch (error) { - logger.error(`[${requestId}] YAML export failed`, error) - return NextResponse.json( - { - success: false, - error: `Failed to export YAML: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - { status: 500 } - ) - } -} diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/[keyId]/route.ts b/apps/sim/app/api/workspaces/[id]/api-keys/[keyId]/route.ts index 34f9909295..f72a86f1d8 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/[keyId]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/[keyId]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { apiKey } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, not } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkspaceApiKeyAPI') diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts index f29df67dc9..0944b15fe2 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { apiKey, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray } from 'drizzle-orm' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' @@ -7,7 +8,6 @@ import { z } from 'zod' import { createApiKey, getApiKeyDisplayFormat } from '@/lib/api-key/auth' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkspaceApiKeysAPI') diff --git a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts new file mode 100644 index 0000000000..246cc6b245 --- /dev/null +++ b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts @@ -0,0 +1,256 @@ +import { db } from '@sim/db' +import { workspace, workspaceBYOKKeys } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { and, eq } from 'drizzle-orm' +import { nanoid } from 'nanoid' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { getSession } from '@/lib/auth' +import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' +import { generateRequestId } from '@/lib/core/utils/request' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('WorkspaceBYOKKeysAPI') + +const VALID_PROVIDERS = ['openai', 'anthropic', 'google', 'mistral'] as const + +const UpsertKeySchema = z.object({ + providerId: z.enum(VALID_PROVIDERS), + apiKey: z.string().min(1, 'API key is required'), +}) + +const DeleteKeySchema = z.object({ + providerId: z.enum(VALID_PROVIDERS), +}) + +function maskApiKey(key: string): string { + if (key.length <= 8) { + return '•'.repeat(8) + } + if (key.length <= 12) { + return `${key.slice(0, 4)}...${key.slice(-4)}` + } + return `${key.slice(0, 6)}...${key.slice(-4)}` +} + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const requestId = generateRequestId() + const workspaceId = (await params).id + + try { + const session = await getSession() + if (!session?.user?.id) { + logger.warn(`[${requestId}] Unauthorized BYOK keys access attempt`) + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const userId = session.user.id + + const ws = await db.select().from(workspace).where(eq(workspace.id, workspaceId)).limit(1) + if (!ws.length) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!permission) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const byokKeys = await db + .select({ + id: workspaceBYOKKeys.id, + providerId: workspaceBYOKKeys.providerId, + encryptedApiKey: workspaceBYOKKeys.encryptedApiKey, + createdBy: workspaceBYOKKeys.createdBy, + createdAt: workspaceBYOKKeys.createdAt, + updatedAt: workspaceBYOKKeys.updatedAt, + }) + .from(workspaceBYOKKeys) + .where(eq(workspaceBYOKKeys.workspaceId, workspaceId)) + .orderBy(workspaceBYOKKeys.providerId) + + const formattedKeys = await Promise.all( + byokKeys.map(async (key) => { + try { + const { decrypted } = await decryptSecret(key.encryptedApiKey) + return { + id: key.id, + providerId: key.providerId, + maskedKey: maskApiKey(decrypted), + createdBy: key.createdBy, + createdAt: key.createdAt, + updatedAt: key.updatedAt, + } + } catch (error) { + logger.error(`[${requestId}] Failed to decrypt BYOK key for provider ${key.providerId}`, { + error, + }) + return { + id: key.id, + providerId: key.providerId, + maskedKey: '••••••••', + createdBy: key.createdBy, + createdAt: key.createdAt, + updatedAt: key.updatedAt, + } + } + }) + ) + + return NextResponse.json({ keys: formattedKeys }) + } catch (error: unknown) { + logger.error(`[${requestId}] BYOK keys GET error`, error) + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to load BYOK keys' }, + { status: 500 } + ) + } +} + +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const requestId = generateRequestId() + const workspaceId = (await params).id + + try { + const session = await getSession() + if (!session?.user?.id) { + logger.warn(`[${requestId}] Unauthorized BYOK key creation attempt`) + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const userId = session.user.id + + const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (permission !== 'admin') { + return NextResponse.json( + { error: 'Only workspace admins can manage BYOK keys' }, + { status: 403 } + ) + } + + const body = await request.json() + const { providerId, apiKey } = UpsertKeySchema.parse(body) + + const { encrypted } = await encryptSecret(apiKey) + + const existingKey = await db + .select() + .from(workspaceBYOKKeys) + .where( + and( + eq(workspaceBYOKKeys.workspaceId, workspaceId), + eq(workspaceBYOKKeys.providerId, providerId) + ) + ) + .limit(1) + + if (existingKey.length > 0) { + await db + .update(workspaceBYOKKeys) + .set({ + encryptedApiKey: encrypted, + updatedAt: new Date(), + }) + .where(eq(workspaceBYOKKeys.id, existingKey[0].id)) + + logger.info(`[${requestId}] Updated BYOK key for ${providerId} in workspace ${workspaceId}`) + + return NextResponse.json({ + success: true, + key: { + id: existingKey[0].id, + providerId, + maskedKey: maskApiKey(apiKey), + updatedAt: new Date(), + }, + }) + } + + const [newKey] = await db + .insert(workspaceBYOKKeys) + .values({ + id: nanoid(), + workspaceId, + providerId, + encryptedApiKey: encrypted, + createdBy: userId, + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning({ + id: workspaceBYOKKeys.id, + providerId: workspaceBYOKKeys.providerId, + createdAt: workspaceBYOKKeys.createdAt, + }) + + logger.info(`[${requestId}] Created BYOK key for ${providerId} in workspace ${workspaceId}`) + + return NextResponse.json({ + success: true, + key: { + ...newKey, + maskedKey: maskApiKey(apiKey), + }, + }) + } catch (error: unknown) { + logger.error(`[${requestId}] BYOK key POST error`, error) + if (error instanceof z.ZodError) { + return NextResponse.json({ error: error.errors[0].message }, { status: 400 }) + } + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to save BYOK key' }, + { status: 500 } + ) + } +} + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const requestId = generateRequestId() + const workspaceId = (await params).id + + try { + const session = await getSession() + if (!session?.user?.id) { + logger.warn(`[${requestId}] Unauthorized BYOK key deletion attempt`) + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const userId = session.user.id + + const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (permission !== 'admin') { + return NextResponse.json( + { error: 'Only workspace admins can manage BYOK keys' }, + { status: 403 } + ) + } + + const body = await request.json() + const { providerId } = DeleteKeySchema.parse(body) + + const result = await db + .delete(workspaceBYOKKeys) + .where( + and( + eq(workspaceBYOKKeys.workspaceId, workspaceId), + eq(workspaceBYOKKeys.providerId, providerId) + ) + ) + + logger.info(`[${requestId}] Deleted BYOK key for ${providerId} from workspace ${workspaceId}`) + + return NextResponse.json({ success: true }) + } catch (error: unknown) { + logger.error(`[${requestId}] BYOK key DELETE error`, error) + if (error instanceof z.ZodError) { + return NextResponse.json({ error: error.errors[0].message }, { status: 400 }) + } + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Failed to delete BYOK key' }, + { status: 500 } + ) + } +} diff --git a/apps/sim/app/api/workspaces/[id]/duplicate/route.ts b/apps/sim/app/api/workspaces/[id]/duplicate/route.ts index 1354bec588..50f1d9c2ff 100644 --- a/apps/sim/app/api/workspaces/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workspaces/[id]/duplicate/route.ts @@ -1,8 +1,8 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { duplicateWorkspace } from '@/lib/workspaces/duplicate' const logger = createLogger('WorkspaceDuplicateAPI') diff --git a/apps/sim/app/api/workspaces/[id]/environment/route.ts b/apps/sim/app/api/workspaces/[id]/environment/route.ts index 8328cf19e1..9c1ee4eb04 100644 --- a/apps/sim/app/api/workspaces/[id]/environment/route.ts +++ b/apps/sim/app/api/workspaces/[id]/environment/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { environment, workspace, workspaceEnvironment } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkspaceEnvironmentAPI') diff --git a/apps/sim/app/api/workspaces/[id]/files/[fileId]/download/route.ts b/apps/sim/app/api/workspaces/[id]/files/[fileId]/download/route.ts index f3719ab874..c35f283060 100644 --- a/apps/sim/app/api/workspaces/[id]/files/[fileId]/download/route.ts +++ b/apps/sim/app/api/workspaces/[id]/files/[fileId]/download/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workspaces/[id]/files/[fileId]/route.ts b/apps/sim/app/api/workspaces/[id]/files/[fileId]/route.ts index cf00bd1dd4..2c646d8e1d 100644 --- a/apps/sim/app/api/workspaces/[id]/files/[fileId]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/files/[fileId]/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { deleteWorkspaceFile } from '@/lib/uploads/contexts/workspace' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/files/route.ts b/apps/sim/app/api/workspaces/[id]/files/route.ts index 7527081008..22a4233b0f 100644 --- a/apps/sim/app/api/workspaces/[id]/files/route.ts +++ b/apps/sim/app/api/workspaces/[id]/files/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { listWorkspaceFiles, uploadWorkspaceFile } from '@/lib/uploads/contexts/workspace' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' diff --git a/apps/sim/app/api/workspaces/[id]/metrics/executions/route.ts b/apps/sim/app/api/workspaces/[id]/metrics/executions/route.ts index 3b424a25cd..4af974b0f3 100644 --- a/apps/sim/app/api/workspaces/[id]/metrics/executions/route.ts +++ b/apps/sim/app/api/workspaces/[id]/metrics/executions/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { pausedExecutions, permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('MetricsExecutionsAPI') diff --git a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts index 799d148a64..7e472af53b 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { workflow, workspaceNotificationSubscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { createLogger } from '@/lib/logs/console/logger' import { ALL_TRIGGER_TYPES } from '@/lib/logs/types' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { MAX_EMAIL_RECIPIENTS, MAX_WORKFLOW_IDS } from '../constants' diff --git a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts index 3cc3c3733d..3e95e22205 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts @@ -1,12 +1,12 @@ import { createHmac } from 'crypto' import { db } from '@sim/db' import { account, workspaceNotificationSubscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { decryptSecret } from '@/lib/core/security/encryption' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/[id]/notifications/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/route.ts index b1aa69ae0a..2716a7ea57 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/route.ts @@ -1,12 +1,12 @@ import { db } from '@sim/db' import { workflow, workspaceNotificationSubscription } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' -import { createLogger } from '@/lib/logs/console/logger' import { ALL_TRIGGER_TYPES } from '@/lib/logs/types' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { MAX_EMAIL_RECIPIENTS, MAX_NOTIFICATIONS_PER_TYPE, MAX_WORKFLOW_IDS } from './constants' diff --git a/apps/sim/app/api/workspaces/[id]/permissions/route.ts b/apps/sim/app/api/workspaces/[id]/permissions/route.ts index 4c2e0dae3e..0025c90fc0 100644 --- a/apps/sim/app/api/workspaces/[id]/permissions/route.ts +++ b/apps/sim/app/api/workspaces/[id]/permissions/route.ts @@ -1,11 +1,11 @@ import crypto from 'crypto' import { db } from '@sim/db' import { permissions, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { getUsersWithPermissions, hasWorkspaceAdminAccess, diff --git a/apps/sim/app/api/workspaces/[id]/route.ts b/apps/sim/app/api/workspaces/[id]/route.ts index 7a77319b50..eed710c7c4 100644 --- a/apps/sim/app/api/workspaces/[id]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/route.ts @@ -1,9 +1,9 @@ import { workflow } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('WorkspaceByIdAPI') diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts index ecbb92a977..12833c9695 100644 --- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts +++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.test.ts @@ -1,3 +1,4 @@ +import { createSession, createWorkspaceRecord, loggerMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -59,14 +60,7 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ mockHasWorkspaceAdminAccess(userId, workspaceId), })) -vi.mock('@/lib/logs/console/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - debug: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - }), -})) +vi.mock('@sim/logger', () => loggerMock) vi.mock('@/lib/core/utils/urls', () => ({ getBaseUrl: vi.fn().mockReturnValue('https://test.sim.ai'), @@ -127,9 +121,14 @@ const mockUser = { name: 'Test User', } -const mockWorkspace = { +const mockWorkspaceData = createWorkspaceRecord({ id: 'workspace-456', name: 'Test Workspace', +}) + +const mockWorkspace = { + id: mockWorkspaceData.id, + name: mockWorkspaceData.name, } const mockInvitation = { @@ -140,7 +139,7 @@ const mockInvitation = { status: 'pending', token: 'token-abc123', permissions: 'read', - expiresAt: new Date(Date.now() + 86400000), // 1 day from now + expiresAt: new Date(Date.now() + 86400000), createdAt: new Date(), updatedAt: new Date(), } @@ -154,7 +153,8 @@ describe('Workspace Invitation [invitationId] API Route', () => { describe('GET /api/workspaces/invitations/[invitationId]', () => { it('should return invitation details when called without token', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) dbSelectResults = [[mockInvitation], [mockWorkspace]] const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789') @@ -202,15 +202,18 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should accept invitation when called with valid token', async () => { - mockGetSession.mockResolvedValue({ - user: { ...mockUser, email: 'invited@example.com' }, + const session = createSession({ + userId: mockUser.id, + email: 'invited@example.com', + name: mockUser.name, }) + mockGetSession.mockResolvedValue(session) dbSelectResults = [ - [mockInvitation], // invitation lookup - [mockWorkspace], // workspace lookup - [{ ...mockUser, email: 'invited@example.com' }], // user lookup - [], // existing permission check (empty = no existing) + [mockInvitation], + [mockWorkspace], + [{ ...mockUser, email: 'invited@example.com' }], + [], ] const request = new NextRequest( @@ -225,13 +228,16 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should redirect to error page when invitation expired', async () => { - mockGetSession.mockResolvedValue({ - user: { ...mockUser, email: 'invited@example.com' }, + const session = createSession({ + userId: mockUser.id, + email: 'invited@example.com', + name: mockUser.name, }) + mockGetSession.mockResolvedValue(session) const expiredInvitation = { ...mockInvitation, - expiresAt: new Date(Date.now() - 86400000), // 1 day ago + expiresAt: new Date(Date.now() - 86400000), } dbSelectResults = [[expiredInvitation], [mockWorkspace]] @@ -250,9 +256,12 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should redirect to error page when email mismatch', async () => { - mockGetSession.mockResolvedValue({ - user: { ...mockUser, email: 'wrong@example.com' }, + const session = createSession({ + userId: mockUser.id, + email: 'wrong@example.com', + name: mockUser.name, }) + mockGetSession.mockResolvedValue(session) dbSelectResults = [ [mockInvitation], @@ -274,8 +283,9 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should return 404 when invitation not found', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) - dbSelectResults = [[]] // Empty result + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) + dbSelectResults = [[]] const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent') const params = Promise.resolve({ invitationId: 'non-existent' }) @@ -306,7 +316,8 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should return 404 when invitation does not exist', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) dbSelectResults = [[]] const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent', { @@ -322,7 +333,8 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should return 403 when user lacks admin access', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(false) dbSelectResults = [[mockInvitation]] @@ -341,7 +353,8 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should return 400 when trying to delete non-pending invitation', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(true) const acceptedInvitation = { ...mockInvitation, status: 'accepted' } @@ -361,7 +374,8 @@ describe('Workspace Invitation [invitationId] API Route', () => { }) it('should successfully delete pending invitation when user has admin access', async () => { - mockGetSession.mockResolvedValue({ user: mockUser }) + const session = createSession({ userId: mockUser.id, email: mockUser.email }) + mockGetSession.mockResolvedValue(session) mockHasWorkspaceAdminAccess.mockResolvedValue(true) dbSelectResults = [[mockInvitation]] diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts index 879624ac1b..0d427f1779 100644 --- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts @@ -8,12 +8,12 @@ import { workspace, workspaceInvitation, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitation' import { getSession } from '@/lib/auth' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils' diff --git a/apps/sim/app/api/workspaces/invitations/route.ts b/apps/sim/app/api/workspaces/invitations/route.ts index 62cfff3d97..6ad6285b35 100644 --- a/apps/sim/app/api/workspaces/invitations/route.ts +++ b/apps/sim/app/api/workspaces/invitations/route.ts @@ -9,12 +9,12 @@ import { workspace, workspaceInvitation, } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq, inArray } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitation' import { getSession } from '@/lib/auth' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' diff --git a/apps/sim/app/api/workspaces/members/[id]/route.ts b/apps/sim/app/api/workspaces/members/[id]/route.ts index b835d89336..ec990da241 100644 --- a/apps/sim/app/api/workspaces/members/[id]/route.ts +++ b/apps/sim/app/api/workspaces/members/[id]/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { permissions, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkspaceMemberAPI') diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index b052d60495..6b8c36ba31 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -1,10 +1,10 @@ import { db } from '@sim/db' import { permissions, workflow, workspace } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { and, desc, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' diff --git a/apps/sim/app/api/yaml/autolayout/route.ts b/apps/sim/app/api/yaml/autolayout/route.ts index 3361813854..600212340f 100644 --- a/apps/sim/app/api/yaml/autolayout/route.ts +++ b/apps/sim/app/api/yaml/autolayout/route.ts @@ -1,7 +1,7 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { generateRequestId } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { applyAutoLayout } from '@/lib/workflows/autolayout' import { DEFAULT_HORIZONTAL_SPACING, diff --git a/apps/sim/app/chat/[identifier]/chat.tsx b/apps/sim/app/chat/[identifier]/chat.tsx index 96be0631d7..9625ada59a 100644 --- a/apps/sim/app/chat/[identifier]/chat.tsx +++ b/apps/sim/app/chat/[identifier]/chat.tsx @@ -1,9 +1,9 @@ 'use client' import { type RefObject, useCallback, useEffect, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { v4 as uuidv4 } from 'uuid' import { noop } from '@/lib/core/utils/request' -import { createLogger } from '@/lib/logs/console/logger' import { getFormattedGitHubStars } from '@/app/(landing)/actions/github' import { ChatErrorState, @@ -117,7 +117,7 @@ export default function ChatClient({ identifier }: { identifier: string }) { const [error, setError] = useState(null) const messagesEndRef = useRef(null) const messagesContainerRef = useRef(null) - const [starCount, setStarCount] = useState('24k') + const [starCount, setStarCount] = useState('24.4k') const [conversationId, setConversationId] = useState('') const [showScrollButton, setShowScrollButton] = useState(false) diff --git a/apps/sim/app/chat/components/auth/email/email-auth.tsx b/apps/sim/app/chat/components/auth/email/email-auth.tsx index 63281b454e..fb2a5d8036 100644 --- a/apps/sim/app/chat/components/auth/email/email-auth.tsx +++ b/apps/sim/app/chat/components/auth/email/email-auth.tsx @@ -1,13 +1,12 @@ 'use client' import { type KeyboardEvent, useEffect, useState } from 'react' -import { Loader2 } from 'lucide-react' +import { createLogger } from '@sim/logger' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp' import { Label } from '@/components/ui/label' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' @@ -299,14 +298,7 @@ export default function EmailAuth({ className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`} disabled={isSendingOtp} > - {isSendingOtp ? ( - <> - - Sending Code... - - ) : ( - 'Continue' - )} + {isSendingOtp ? 'Sending Code...' : 'Continue'} ) : ( diff --git a/apps/sim/app/chat/components/auth/password/password-auth.tsx b/apps/sim/app/chat/components/auth/password/password-auth.tsx index e132e9562b..f99847f73f 100644 --- a/apps/sim/app/chat/components/auth/password/password-auth.tsx +++ b/apps/sim/app/chat/components/auth/password/password-auth.tsx @@ -1,12 +1,12 @@ 'use client' import { type KeyboardEvent, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { Eye, EyeOff } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' import Nav from '@/app/(landing)/components/nav/nav' diff --git a/apps/sim/app/chat/components/auth/sso/sso-auth.tsx b/apps/sim/app/chat/components/auth/sso/sso-auth.tsx index fca79215f7..8ceb4bf557 100644 --- a/apps/sim/app/chat/components/auth/sso/sso-auth.tsx +++ b/apps/sim/app/chat/components/auth/sso/sso-auth.tsx @@ -1,12 +1,12 @@ 'use client' import { type KeyboardEvent, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { inter } from '@/app/_styles/fonts/inter/inter' import { soehne } from '@/app/_styles/fonts/soehne/soehne' diff --git a/apps/sim/app/chat/components/input/input.tsx b/apps/sim/app/chat/components/input/input.tsx index 132a573ed2..ea41dbb954 100644 --- a/apps/sim/app/chat/components/input/input.tsx +++ b/apps/sim/app/chat/components/input/input.tsx @@ -9,7 +9,7 @@ import { VoiceInput } from '@/app/chat/components/input/voice-input' const logger = createLogger('ChatInput') -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' const PLACEHOLDER_MOBILE = 'Enter a message' const PLACEHOLDER_DESKTOP = 'Enter a message or click the mic to speak' diff --git a/apps/sim/app/chat/components/message/components/file-download.tsx b/apps/sim/app/chat/components/message/components/file-download.tsx index 7be5237b16..be884b1a2a 100644 --- a/apps/sim/app/chat/components/message/components/file-download.tsx +++ b/apps/sim/app/chat/components/message/components/file-download.tsx @@ -1,10 +1,10 @@ 'use client' import { useState } from 'react' +import { createLogger } from '@sim/logger' import { ArrowDown, Download, Loader2, Music } from 'lucide-react' import { Button } from '@/components/emcn' import { DefaultFileIcon, getDocumentIcon } from '@/components/icons/document-icons' -import { createLogger } from '@/lib/logs/console/logger' import type { ChatFile } from '@/app/chat/components/message/message' const logger = createLogger('ChatFileDownload') diff --git a/apps/sim/app/chat/components/voice-interface/components/particles.tsx b/apps/sim/app/chat/components/voice-interface/components/particles.tsx index e383e47dd1..3b206e3369 100644 --- a/apps/sim/app/chat/components/voice-interface/components/particles.tsx +++ b/apps/sim/app/chat/components/voice-interface/components/particles.tsx @@ -1,8 +1,8 @@ 'use client' import { useCallback, useEffect, useRef } from 'react' +import { createLogger } from '@sim/logger' import * as THREE from 'three' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('Particles') diff --git a/apps/sim/app/chat/components/voice-interface/voice-interface.tsx b/apps/sim/app/chat/components/voice-interface/voice-interface.tsx index d4dc002ff2..94411a0e29 100644 --- a/apps/sim/app/chat/components/voice-interface/voice-interface.tsx +++ b/apps/sim/app/chat/components/voice-interface/voice-interface.tsx @@ -1,10 +1,10 @@ 'use client' import { type RefObject, useCallback, useEffect, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { Mic, MicOff, Phone } from 'lucide-react' import { Button } from '@/components/ui/button' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { ParticlesVisualization } from '@/app/chat/components/voice-interface/components/particles' const logger = createLogger('VoiceInterface') diff --git a/apps/sim/app/chat/hooks/use-audio-streaming.ts b/apps/sim/app/chat/hooks/use-audio-streaming.ts index b37e7ac83c..b7bda6208e 100644 --- a/apps/sim/app/chat/hooks/use-audio-streaming.ts +++ b/apps/sim/app/chat/hooks/use-audio-streaming.ts @@ -1,7 +1,7 @@ 'use client' import { type RefObject, useCallback, useRef, useState } from 'react' -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' const logger = createLogger('UseAudioStreaming') diff --git a/apps/sim/app/chat/hooks/use-chat-streaming.ts b/apps/sim/app/chat/hooks/use-chat-streaming.ts index 40960684ad..ac474fa377 100644 --- a/apps/sim/app/chat/hooks/use-chat-streaming.ts +++ b/apps/sim/app/chat/hooks/use-chat-streaming.ts @@ -1,8 +1,8 @@ 'use client' import { useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { isUserFile } from '@/lib/core/utils/display-filters' -import { createLogger } from '@/lib/logs/console/logger' import type { ChatFile, ChatMessage } from '@/app/chat/components/message/message' import { CHAT_ERROR_MESSAGES } from '@/app/chat/constants' diff --git a/apps/sim/app/invite/[id]/invite.tsx b/apps/sim/app/invite/[id]/invite.tsx index 70d66cd057..25a7493cfa 100644 --- a/apps/sim/app/invite/[id]/invite.tsx +++ b/apps/sim/app/invite/[id]/invite.tsx @@ -1,9 +1,9 @@ 'use client' import { useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { useParams, useRouter, useSearchParams } from 'next/navigation' import { client, useSession } from '@/lib/auth/auth-client' -import { createLogger } from '@/lib/logs/console/logger' import { InviteLayout, InviteStatusCard } from '@/app/invite/components' const logger = createLogger('InviteById') diff --git a/apps/sim/app/invite/components/status-card.tsx b/apps/sim/app/invite/components/status-card.tsx index 65a2a30dc3..043300b080 100644 --- a/apps/sim/app/invite/components/status-card.tsx +++ b/apps/sim/app/invite/components/status-card.tsx @@ -162,14 +162,7 @@ export function InviteStatusCard({ onClick={action.onClick} disabled={action.disabled || action.loading} > - {action.loading ? ( - <> - - {action.label}... - - ) : ( - action.label - )} + {action.loading ? `${action.label}...` : action.label} ))} diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index bb40e8156f..da80a06d21 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -11,7 +11,6 @@ import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler' import { QueryProvider } from '@/app/_shell/providers/query-provider' import { SessionProvider } from '@/app/_shell/providers/session-provider' import { ThemeProvider } from '@/app/_shell/providers/theme-provider' -import { ZoomPrevention } from '@/app/_shell/zoom-prevention' import { season } from '@/app/_styles/fonts/season/season' export const viewport: Viewport = { @@ -85,7 +84,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) var panelWidth = panelState && panelState.panelWidth; var maxPanelWidth = window.innerWidth * 0.4; - if (panelWidth >= 244 && panelWidth <= maxPanelWidth) { + if (panelWidth >= 260 && panelWidth <= maxPanelWidth) { document.documentElement.style.setProperty('--panel-width', panelWidth + 'px'); } else if (panelWidth > maxPanelWidth) { document.documentElement.style.setProperty('--panel-width', maxPanelWidth + 'px'); @@ -190,10 +189,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - - - {children} - + {children} diff --git a/apps/sim/app/playground/page.tsx b/apps/sim/app/playground/page.tsx index 7a3804211a..8e3fb2fa22 100644 --- a/apps/sim/app/playground/page.tsx +++ b/apps/sim/app/playground/page.tsx @@ -317,10 +317,10 @@ export default function PlaygroundPage() { - - + + - + Item 1 @@ -550,7 +550,7 @@ export default function PlaygroundPage() { ].map(({ Icon, name }) => ( -
+
diff --git a/apps/sim/app/templates/[id]/page.tsx b/apps/sim/app/templates/[id]/page.tsx index 9a5a47f627..af9348be35 100644 --- a/apps/sim/app/templates/[id]/page.tsx +++ b/apps/sim/app/templates/[id]/page.tsx @@ -1,9 +1,9 @@ import { db } from '@sim/db' import { templateCreators, templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { Metadata } from 'next' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import TemplateDetails from '@/app/templates/[id]/template' const logger = createLogger('TemplateMetadata') diff --git a/apps/sim/app/templates/[id]/template.tsx b/apps/sim/app/templates/[id]/template.tsx index 9b8c32a00a..9619bcb553 100644 --- a/apps/sim/app/templates/[id]/template.tsx +++ b/apps/sim/app/templates/[id]/template.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { formatDistanceToNow } from 'date-fns' import { ChartNoAxesColumn, @@ -34,7 +35,6 @@ import { VerifiedBadge } from '@/components/ui/verified-badge' import { useSession } from '@/lib/auth/auth-client' import { cn } from '@/lib/core/utils/cn' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import type { CredentialRequirement } from '@/lib/workflows/credentials/credential-extractor' import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview' import { getBlock } from '@/blocks/registry' @@ -732,7 +732,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template <> {!currentUserId ? ( ) : isWorkspaceContext ? (
) : ( -
+
)} @@ -1001,7 +1001,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template />
) : ( -
+
)} diff --git a/apps/sim/app/templates/components/template-card.tsx b/apps/sim/app/templates/components/template-card.tsx index 35d23e5766..071d01b6e5 100644 --- a/apps/sim/app/templates/components/template-card.tsx +++ b/apps/sim/app/templates/components/template-card.tsx @@ -1,9 +1,9 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { Star, User } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { VerifiedBadge } from '@/components/ui/verified-badge' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview' import { getBlock } from '@/blocks/registry' import { useStarTemplate } from '@/hooks/queries/templates' @@ -27,15 +27,15 @@ interface TemplateCardProps { export function TemplateCardSkeleton({ className }: { className?: string }) { return ( -
-
+
+
-
+
{Array.from({ length: 3 }).map((_, index) => (
))}
@@ -43,14 +43,14 @@ export function TemplateCardSkeleton({ className }: { className?: string }) {
-
-
+
+
-
-
-
-
+
+
+
+
@@ -195,7 +195,7 @@ function TemplateCardInner({ return (
) : ( -
+
)}
-

{title}

+

+ {title} +

{blockTypes.length > 4 ? ( @@ -241,10 +243,12 @@ function TemplateCardInner({ ) })}
- +{blockTypes.length - 3} + + +{blockTypes.length - 3} +
) : ( @@ -276,24 +280,26 @@ function TemplateCardInner({ {author}
) : ( -
- +
+
)}
- {author} + + {author} + {isVerified && }
-
+
{usageCount} diff --git a/apps/sim/app/templates/templates.tsx b/apps/sim/app/templates/templates.tsx index 46e5c447ec..cbb3ab6938 100644 --- a/apps/sim/app/templates/templates.tsx +++ b/apps/sim/app/templates/templates.tsx @@ -1,11 +1,11 @@ 'use client' import { useEffect, useMemo, useState } from 'react' +import { createLogger } from '@sim/logger' import { Layout, Search } from 'lucide-react' import { useRouter } from 'next/navigation' import { Button } from '@/components/emcn' import { Input } from '@/components/ui/input' -import { createLogger } from '@/lib/logs/console/logger' import type { CredentialRequirement } from '@/lib/workflows/credentials/credential-extractor' import type { CreatorProfileDetails } from '@/app/_types/creator-profile' import { TemplateCard, TemplateCardSkeleton } from '@/app/templates/components/template-card' @@ -149,7 +149,7 @@ export default function Templates({
-
+
- {processing ? ( - - ) : data?.currentPreferences.unsubscribeAll ? ( + {data?.currentPreferences.unsubscribeAll ? ( ) : null} - {data?.currentPreferences.unsubscribeAll - ? 'Unsubscribed from All Emails' - : 'Unsubscribe from All Marketing Emails'} + {processing + ? 'Unsubscribing...' + : data?.currentPreferences.unsubscribeAll + ? 'Unsubscribed from All Emails' + : 'Unsubscribe from All Marketing Emails'}
diff --git a/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx b/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx index 778217a961..fb858aa0d4 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx @@ -1,6 +1,6 @@ 'use client' -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace' const logger = createLogger('FileViewer') diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/create-chunk-modal/create-chunk-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/create-chunk-modal/create-chunk-modal.tsx index c1952eed35..876da5f081 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/create-chunk-modal/create-chunk-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/create-chunk-modal/create-chunk-modal.tsx @@ -1,7 +1,8 @@ 'use client' import { useRef, useState } from 'react' -import { AlertCircle, Loader2 } from 'lucide-react' +import { createLogger } from '@sim/logger' +import { AlertCircle } from 'lucide-react' import { Button, Label, @@ -12,7 +13,6 @@ import { ModalHeader, Textarea, } from '@/components/emcn' -import { createLogger } from '@/lib/logs/console/logger' import type { ChunkData, DocumentData } from '@/stores/knowledge/store' const logger = createLogger('CreateChunkModal') @@ -157,19 +157,12 @@ export function CreateChunkModal({ Cancel @@ -181,7 +174,7 @@ export function CreateChunkModal({ Discard Changes -

+

You have unsaved changes. Are you sure you want to close without saving?

@@ -193,12 +186,7 @@ export function CreateChunkModal({ > Keep Editing - diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/delete-chunk-modal/delete-chunk-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/delete-chunk-modal/delete-chunk-modal.tsx index 96115db673..c9560493ed 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/delete-chunk-modal/delete-chunk-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/delete-chunk-modal/delete-chunk-modal.tsx @@ -1,8 +1,8 @@ 'use client' import { useState } from 'react' +import { createLogger } from '@sim/logger' import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn' -import { createLogger } from '@/lib/logs/console/logger' import type { ChunkData } from '@/stores/knowledge/store' const logger = createLogger('DeleteChunkModal') @@ -69,7 +69,7 @@ export function DeleteChunkModal({ Delete Chunk -

+

Are you sure you want to delete this chunk?{' '} This action cannot be undone.

@@ -78,12 +78,7 @@ export function DeleteChunkModal({ - diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx index 1f40cd20dd..9bbd328bd5 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-tags-modal/document-tags-modal.tsx @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import { Loader2 } from 'lucide-react' +import { createLogger } from '@sim/logger' import { Button, Combobox, @@ -18,7 +18,6 @@ import { import { cn } from '@/lib/core/utils/cn' import { ALL_TAG_SLOTS, type AllTagSlot, MAX_TAG_SLOTS } from '@/lib/knowledge/constants' import type { DocumentTag } from '@/lib/knowledge/tags/types' -import { createLogger } from '@/lib/logs/console/logger' import { type TagDefinition, useKnowledgeBaseTagDefinitions, @@ -575,7 +574,7 @@ export function DocumentTagsModal({ Cancel
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/edit-chunk-modal/edit-chunk-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/edit-chunk-modal/edit-chunk-modal.tsx index 368a5df579..389dedb36e 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/edit-chunk-modal/edit-chunk-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/edit-chunk-modal/edit-chunk-modal.tsx @@ -2,7 +2,8 @@ import { useEffect, useState } from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' -import { AlertCircle, ChevronDown, ChevronUp, Loader2, X } from 'lucide-react' +import { createLogger } from '@sim/logger' +import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react' import { Button, Label, @@ -14,7 +15,6 @@ import { Textarea, Tooltip, } from '@/components/emcn' -import { createLogger } from '@/lib/logs/console/logger' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import type { ChunkData, DocumentData } from '@/stores/knowledge/store' @@ -275,19 +275,12 @@ export function EditChunkModal({ {userPermissions.canEdit && ( )} @@ -300,7 +293,7 @@ export function EditChunkModal({ Unsaved Changes -

+

You have unsaved changes to this chunk content. {pendingNavigation ? ' Do you want to discard your changes and navigate to the next chunk?' @@ -318,12 +311,7 @@ export function EditChunkModal({ > Keep Editing - diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx index 494139db99..7b2d278cd4 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx @@ -1,6 +1,7 @@ 'use client' import { startTransition, useCallback, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { useQueryClient } from '@tanstack/react-query' import { ChevronLeft, @@ -36,7 +37,6 @@ import { TableHeader, TableRow, } from '@/components/ui/table' -import { createLogger } from '@/lib/logs/console/logger' import { CreateChunkModal, DeleteChunkModal, @@ -234,7 +234,7 @@ function DocumentLoading({

-
+
-
@@ -851,7 +851,7 @@ export function Document({
-
+
setIsCreateChunkModalOpen(true)} disabled={documentData?.processingStatus === 'failed' || !userPermissions.canEdit} - variant='primary' + variant='tertiary' className='h-[32px] rounded-[6px]' > Create Chunk @@ -1092,18 +1092,13 @@ export function Document({ )} {documentData?.processingStatus === 'completed' && totalPages > 1 && ( -
+
- -
+
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let page: number if (totalPages <= 5) { @@ -1133,13 +1128,8 @@ export function Document({ })}
-
@@ -1229,7 +1219,7 @@ export function Document({ Delete Document -

+

Are you sure you want to delete "{effectiveDocumentName}"? This will permanently delete the document and all {documentData?.chunkCount ?? 0} chunk {documentData?.chunkCount === 1 ? '' : 's'} within it.{' '} @@ -1245,10 +1235,9 @@ export function Document({ Cancel diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 2c4ce5a3a8..8274a9dd3a 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -1,6 +1,7 @@ 'use client' import { useCallback, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { format } from 'date-fns' import { AlertCircle, @@ -17,6 +18,7 @@ import { } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { + Badge, Breadcrumb, Button, Modal, @@ -39,8 +41,8 @@ import { TableHeader, TableRow, } from '@/components/ui/table' +import { cn } from '@/lib/core/utils/cn' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' -import { createLogger } from '@/lib/logs/console/logger' import { ActionBar, AddDocumentsModal, @@ -53,6 +55,10 @@ import { useKnowledgeBaseDocuments, useKnowledgeBasesList, } from '@/hooks/use-knowledge' +import { + type TagDefinition, + useKnowledgeBaseTagDefinitions, +} from '@/hooks/use-knowledge-base-tag-definitions' import type { DocumentData } from '@/stores/knowledge/store' const logger = createLogger('KnowledgeBase') @@ -73,28 +79,27 @@ function DocumentTableRowSkeleton() {

- + - + - + -
-
- - | - -
- -
+
+ +
+ + +
+
@@ -118,22 +123,25 @@ function DocumentTableSkeleton({ rowCount = 5 }: { rowCount?: number }) { Name - + Size - + Tokens - + Chunks - + Uploaded - + Status - + + Tags + + Actions @@ -185,7 +193,7 @@ function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoadingProps)
-
+
-
@@ -274,56 +282,122 @@ function formatFileSize(bytes: number): string { return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}` } -const getStatusDisplay = (doc: DocumentData) => { - // Consolidated status: show processing status when not completed, otherwise show enabled/disabled +const AnimatedLoader = ({ className }: { className?: string }) => ( + +) + +const getStatusBadge = (doc: DocumentData) => { switch (doc.processingStatus) { case 'pending': - return { - text: 'Pending', - className: - 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300', - } + return ( + + Pending + + ) case 'processing': - return { - text: ( - <> - - Processing - - ), - className: - 'inline-flex items-center rounded-md bg-purple-100 px-2 py-1 text-xs font-medium text-[var(--brand-primary-hex)] dark:bg-purple-900/30 dark:text-[var(--brand-primary-hex)]', - } + return ( + + Processing + + ) case 'failed': - return { - text: ( - <> - Failed - {doc.processingError && } - - ), - className: - 'inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-300', - } + return doc.processingError ? ( + + Failed + + ) : ( + + Failed + + ) case 'completed': - return doc.enabled - ? { - text: 'Enabled', - className: - 'inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400', - } - : { - text: 'Disabled', - className: - 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300', - } + return doc.enabled ? ( + + Enabled + + ) : ( + + Disabled + + ) default: - return { - text: 'Unknown', - className: - 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300', + return ( + + Unknown + + ) + } +} + +const TAG_SLOTS = [ + 'tag1', + 'tag2', + 'tag3', + 'tag4', + 'tag5', + 'tag6', + 'tag7', + 'number1', + 'number2', + 'number3', + 'number4', + 'number5', + 'date1', + 'date2', + 'boolean1', + 'boolean2', + 'boolean3', +] as const + +type TagSlot = (typeof TAG_SLOTS)[number] + +interface TagValue { + slot: TagSlot + displayName: string + value: string +} + +const TAG_FIELD_TYPES: Record = { + tag: 'text', + number: 'number', + date: 'date', + boolean: 'boolean', +} + +/** + * Computes tag values for a document + */ +function getDocumentTags(doc: DocumentData, definitions: TagDefinition[]): TagValue[] { + const result: TagValue[] = [] + + for (const slot of TAG_SLOTS) { + const raw = doc[slot] + if (raw == null) continue + + const def = definitions.find((d) => d.tagSlot === slot) + const fieldType = def?.fieldType || TAG_FIELD_TYPES[slot.replace(/\d+$/, '')] || 'text' + + let value: string + if (fieldType === 'date') { + try { + value = format(new Date(raw as string), 'MMM d, yyyy') + } catch { + value = String(raw) } + } else if (fieldType === 'boolean') { + value = raw ? 'Yes' : 'No' + } else if (fieldType === 'number' && typeof raw === 'number') { + value = raw.toLocaleString() + } else { + value = String(raw) + } + + if (value) { + result.push({ slot, displayName: def?.displayName || slot, value }) + } } + + return result } export function KnowledgeBase({ @@ -379,6 +453,8 @@ export function KnowledgeBase({ sortOrder, }) + const { tagDefinitions } = useKnowledgeBaseTagDefinitions(id) + const router = useRouter() const knowledgeBaseName = knowledgeBase?.name || passedKnowledgeBaseName || 'Knowledge Base' @@ -973,7 +1049,7 @@ export function KnowledgeBase({
-
+
Add Documents @@ -1058,12 +1134,15 @@ export function KnowledgeBase({
{renderSortableHeader('filename', 'Name', 'w-[180px] max-w-[180px]')} - {renderSortableHeader('fileSize', 'Size', 'w-[8%]')} - {renderSortableHeader('tokenCount', 'Tokens', 'w-[8%]')} - {renderSortableHeader('chunkCount', 'Chunks', 'hidden w-[8%] lg:table-cell')} - {renderSortableHeader('uploadedAt', 'Uploaded', 'w-[16%]')} - {renderSortableHeader('processingStatus', 'Status', 'w-[12%]')} - + {renderSortableHeader('fileSize', 'Size', 'hidden w-[8%] lg:table-cell')} + {renderSortableHeader('tokenCount', 'Tokens', 'hidden w-[8%] lg:table-cell')} + {renderSortableHeader('chunkCount', 'Chunks', 'w-[8%]')} + {renderSortableHeader('uploadedAt', 'Uploaded', 'w-[11%]')} + {renderSortableHeader('processingStatus', 'Status', 'w-[10%]')} + + Tags + + Actions @@ -1071,7 +1150,6 @@ export function KnowledgeBase({ {documents.map((doc) => { const isSelected = selectedDocuments.has(doc.id) - const statusDisplay = getStatusDisplay(doc) return (
- + {formatFileSize(doc.fileSize)} - + {doc.processingStatus === 'completed' ? ( doc.tokenCount > 1000 ? ( `${(doc.tokenCount / 1000).toFixed(1)}k` @@ -1129,43 +1207,73 @@ export function KnowledgeBase({ )} - + {doc.processingStatus === 'completed' ? doc.chunkCount.toLocaleString() : '—'} -
-
- {format(new Date(doc.uploadedAt), 'h:mm a')} - - | - - - {format(new Date(doc.uploadedAt), 'MMM d, yyyy')} + + + + {format(new Date(doc.uploadedAt), 'MMM d')} -
-
- {format(new Date(doc.uploadedAt), 'MMM d')} -
-
+ + + {format(new Date(doc.uploadedAt), 'MMM d, yyyy h:mm a')} + +
{doc.processingStatus === 'failed' && doc.processingError ? ( -
- {statusDisplay.text} -
+
{getStatusBadge(doc)}
{doc.processingError}
) : ( -
{statusDisplay.text}
+ getStatusBadge(doc) )}
+ + {(() => { + const tags = getDocumentTags(doc, tagDefinitions) + if (tags.length === 0) { + return + } + const displayText = tags.map((t) => t.value).join(', ') + return ( + + + e.stopPropagation()} + > + {displayText} + + + +
+ {tags.map((tag) => ( +
+ + {tag.displayName}: + {' '} + {tag.value} +
+ ))} +
+
+
+ ) + })()} +
{doc.processingStatus === 'failed' && ( @@ -1253,18 +1361,17 @@ export function KnowledgeBase({ )} {totalPages > 1 && ( -
+
-
+
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let page: number if (totalPages <= 5) { @@ -1298,9 +1405,8 @@ export function KnowledgeBase({ variant='ghost' onClick={nextPage} disabled={!hasNextPage || isLoadingDocuments} - className='h-8 w-8 p-0' > - +
@@ -1315,7 +1421,7 @@ export function KnowledgeBase({ Delete Knowledge Base -

+

Are you sure you want to delete "{knowledgeBaseName}"? This will permanently delete the knowledge base and all {pagination.total} document {pagination.total === 1 ? '' : 's'} within it.{' '} @@ -1330,12 +1436,7 @@ export function KnowledgeBase({ > Cancel - @@ -1346,7 +1447,7 @@ export function KnowledgeBase({ Delete Document -

+

Are you sure you want to delete " {documents.find((doc) => doc.id === documentToDelete)?.filename ?? 'this document'}"?{' '} This action cannot be undone. @@ -1362,11 +1463,7 @@ export function KnowledgeBase({ > Cancel - @@ -1377,7 +1474,7 @@ export function KnowledgeBase({ Delete Documents -

+

Are you sure you want to delete {selectedDocuments.size} document {selectedDocuments.size === 1 ? '' : 's'}?{' '} This action cannot be undone. @@ -1387,12 +1484,7 @@ export function KnowledgeBase({ - - - Enable {disabledCount > 1 ? `${disabledCount} items` : 'item'} - + Enable )} @@ -72,14 +70,12 @@ export function ActionBar({ variant='ghost' onClick={onDisable} disabled={isLoading} - className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]' + className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover:bg-[var(--brand-secondary)]' > - - Disable {enabledCount > 1 ? `${enabledCount} items` : 'item'} - + Disable )} @@ -90,12 +86,12 @@ export function ActionBar({ variant='ghost' onClick={onDelete} disabled={isLoading} - className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]' + className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover:bg-[var(--brand-secondary)]' > - Delete items + Delete )}

diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-documents-modal/add-documents-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-documents-modal/add-documents-modal.tsx index e88c79fe9e..731f51960a 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-documents-modal/add-documents-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-documents-modal/add-documents-modal.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { Loader2, RotateCcw, X } from 'lucide-react' import { useParams } from 'next/navigation' import { @@ -13,7 +14,6 @@ import { ModalHeader, } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils' import { ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation' import { useKnowledgeUpload } from '@/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload' @@ -352,7 +352,7 @@ export function AddDocumentsModal({ Cancel
@@ -468,7 +460,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM Delete Tag
-

+

Are you sure you want to delete the "{selectedTag?.displayName}" tag? This will remove this tag from {selectedTagUsage?.documentCount || 0} document {selectedTagUsage?.documentCount !== 1 ? 's' : ''}.{' '} @@ -494,12 +486,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM > Cancel - diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx index 50ba8d61d7..c643347a55 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/base-card/base-card.tsx @@ -67,26 +67,26 @@ function formatAbsoluteDate(dateString: string): string { */ export function BaseCardSkeleton() { return ( -

+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
@@ -122,9 +122,9 @@ export function BaseCard({ id, title, docCount, description, updatedAt }: BaseCa return ( -
+
-

+

{title}

{shortId && {shortId}} @@ -139,7 +139,7 @@ export function BaseCard({ id, title, docCount, description, updatedAt }: BaseCa {updatedAt && ( - + last updated: {formatRelativeTime(updatedAt)} diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/constants.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/components/constants.ts index 07d6943b84..aa5b7618fe 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/constants.ts +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/constants.ts @@ -1,8 +1,8 @@ export const filterButtonClass = - 'w-full justify-between rounded-[10px] border-[#E5E5E5] bg-[var(--white)] font-normal text-sm dark:border-[#414141] dark:bg-[var(--surface-elevated)]' + 'w-full justify-between rounded-[10px] border-[#E5E5E5] bg-[var(--white)] font-normal text-sm dark:border-[#414141] dark:bg-[var(--surface-2)]' export const dropdownContentClass = - 'w-[220px] rounded-lg border-[#E5E5E5] bg-[var(--white)] p-0 shadow-xs dark:border-[#414141] dark:bg-[var(--surface-elevated)]' + 'w-[220px] rounded-lg border-[#E5E5E5] bg-[var(--white)] p-0 shadow-xs dark:border-[#414141] dark:bg-[var(--surface-2)]' export const commandListClass = 'overflow-y-auto overflow-x-hidden' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx index 928fd52954..ed70512fc0 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { zodResolver } from '@hookform/resolvers/zod' +import { createLogger } from '@sim/logger' import { Loader2, RotateCcw, X } from 'lucide-react' import { useParams } from 'next/navigation' import { useForm } from 'react-hook-form' @@ -18,7 +19,6 @@ import { Textarea, } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils' import { ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation' import { useKnowledgeUpload } from '@/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload' @@ -388,7 +388,7 @@ export function CreateBaseModal({ />
-
+
@@ -562,7 +562,7 @@ export function CreateBaseModal({ Cancel - - - - handleTimeRangeSelect('All time')} - > - All time - - -
- - {specificTimeRanges.map((range) => ( - handleTimeRangeSelect(range)} - > - {range} - - ))} - - - - ) -} - -interface ControlsProps { - searchQuery?: string - setSearchQuery?: (v: string) => void - isRefetching: boolean - resetToNow: () => void - live: boolean - setLive: (v: (prev: boolean) => boolean) => void - viewMode: string - setViewMode: (mode: 'logs' | 'dashboard') => void - searchComponent?: ReactNode - showExport?: boolean - onExport?: () => void - canConfigureNotifications?: boolean - onConfigureNotifications?: () => void -} - -export function Controls({ - searchQuery, - setSearchQuery, - isRefetching, - resetToNow, - live, - setLive, - viewMode, - setViewMode, - searchComponent, - onExport, - canConfigureNotifications, - onConfigureNotifications, -}: ControlsProps) { - return ( -
- {searchComponent ? ( - searchComponent - ) : ( -
- - setSearchQuery?.(e.target.value)} - className='h-9 w-full border-[#E5E5E5] bg-[var(--white)] pr-10 pl-9 dark:border-[#414141] dark:bg-[var(--surface-elevated)]' - /> - {searchQuery && ( - - )} -
- )} - -
- {viewMode !== 'dashboard' && ( - - - - - - - - - Export as CSV - - - - Configure Notifications - - - - - )} - - - - - - {isRefetching ? 'Refreshing...' : 'Refresh'} - - -
- -
- -
- - -
-
- -
- -
-
- ) -} - -export default Controls diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/controls/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/controls/index.ts deleted file mode 100644 index 0c5bf9faf6..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/controls/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Controls, default } from './controls' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx index 0370641997..ac17cde6b1 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx @@ -1,9 +1,9 @@ 'use client' import { useCallback, useEffect, useState } from 'react' +import { createLogger } from '@sim/logger' import { Hash, Lock } from 'lucide-react' import { Combobox, type ComboboxOption } from '@/components/emcn' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SlackChannelSelector') diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx index 81bedb0393..972b930b40 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx @@ -1,6 +1,7 @@ 'use client' import { useCallback, useEffect, useMemo, useState } from 'react' +import { createLogger } from '@sim/logger' import { AlertCircle, Plus, X } from 'lucide-react' import { Badge, @@ -21,7 +22,6 @@ import { import { SlackIcon } from '@/components/icons' import { Skeleton } from '@/components/ui' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { ALL_TRIGGER_TYPES, type TriggerType } from '@/lib/logs/types' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { @@ -39,9 +39,6 @@ import { WorkflowSelector } from './components/workflow-selector' const logger = createLogger('NotificationSettings') -const PRIMARY_BUTTON_STYLES = - '!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:!bg-[var(--brand-tertiary-2)]/90' - type NotificationType = 'webhook' | 'email' | 'slack' type LogLevel = 'info' | 'error' type AlertRule = @@ -618,10 +615,9 @@ export function NotificationSettings({
@@ -1379,7 +1372,7 @@ function EmailTag({ email, onRemove, isInvalid }: EmailTagProps) { 'flex w-auto items-center gap-[4px] rounded-[4px] border px-[6px] py-[2px] text-[12px]', isInvalid ? 'border-[var(--text-error)] bg-[color-mix(in_srgb,var(--text-error)_10%,transparent)] text-[var(--text-error)] dark:bg-[color-mix(in_srgb,var(--text-error)_16%,transparent)]' - : 'border-[var(--surface-11)] bg-[var(--surface-5)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]' + : 'border-[var(--border-1)] bg-[var(--surface-4)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]' )} > {email} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx index e5ccb326db..2569d6c5fb 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx @@ -163,7 +163,7 @@ export function AutocompleteSearch({ }} > -
+
{/* Search Icon */} @@ -266,8 +266,8 @@ export function AutocompleteSearch({ data-index={0} className={cn( 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', - 'hover:bg-[var(--surface-9)]', - highlightedIndex === 0 && 'bg-[var(--surface-9)]' + 'hover:bg-[var(--surface-5)]', + highlightedIndex === 0 && 'bg-[var(--surface-5)]' )} onMouseEnter={() => setHighlightedIndex(0)} onMouseDown={(e) => { @@ -296,8 +296,8 @@ export function AutocompleteSearch({ data-index={index} className={cn( 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', - 'hover:bg-[var(--surface-9)]', - isHighlighted && 'bg-[var(--surface-9)]' + 'hover:bg-[var(--surface-5)]', + isHighlighted && 'bg-[var(--surface-5)]' )} onMouseEnter={() => setHighlightedIndex(index)} onMouseDown={(e) => { @@ -339,8 +339,8 @@ export function AutocompleteSearch({ data-index={index} className={cn( 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', - 'hover:bg-[var(--surface-9)]', - index === highlightedIndex && 'bg-[var(--surface-9)]' + 'hover:bg-[var(--surface-5)]', + index === highlightedIndex && 'bg-[var(--surface-5)]' )} onMouseEnter={() => setHighlightedIndex(index)} onMouseDown={(e) => { diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/index.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/index.ts index a18f450876..2884924bb2 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/index.ts @@ -1,4 +1,3 @@ -export { Controls } from './components/controls' export { NotificationSettings } from './components/notifications' export { AutocompleteSearch } from './components/search' export { LogsToolbar } from './logs-toolbar' diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx index a84c69ac44..c51e2c38cf 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/logs-toolbar.tsx @@ -295,14 +295,14 @@ export function LogsToolbar({ {/* Header Section */}
-
- +
+

Logs

{/* More options popover */} - + {/* View mode toggle */}
onViewModeChange(isDashboardView ? 'logs' : 'dashboard')} > diff --git a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts index e17af8b90a..77d9dd2162 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts @@ -5,10 +5,15 @@ import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options' import { getBlock } from '@/blocks/registry' const CORE_TRIGGER_TYPES = ['manual', 'api', 'schedule', 'chat', 'webhook'] as const -const RUNNING_COLOR = '#22c55e' as const -const PENDING_COLOR = '#f59e0b' as const + +/** Possible execution status values for workflow logs */ export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled' +/** + * Maps raw status string to LogStatus for display. + * @param status - Raw status from API + * @returns Normalized LogStatus value + */ export function getDisplayStatus(status: string | null | undefined): LogStatus { switch (status) { case 'running': @@ -24,108 +29,54 @@ export function getDisplayStatus(status: string | null | undefined): LogStatus { } } -/** - * Checks if a hex color is gray/neutral (low saturation) or too light/dark - */ -export function isGrayOrNeutral(hex: string): boolean { - const r = Number.parseInt(hex.slice(1, 3), 16) - const g = Number.parseInt(hex.slice(3, 5), 16) - const b = Number.parseInt(hex.slice(5, 7), 16) - - const max = Math.max(r, g, b) - const min = Math.min(r, g, b) - const lightness = (max + min) / 2 / 255 - - const delta = max - min - const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1)) / 255 - - return saturation < 0.2 || lightness > 0.8 || lightness < 0.25 +/** Configuration mapping log status to Badge variant and display label */ +const STATUS_VARIANT_MAP: Record< + LogStatus, + { variant: React.ComponentProps['variant']; label: string } +> = { + error: { variant: 'red', label: 'Error' }, + pending: { variant: 'amber', label: 'Pending' }, + running: { variant: 'green', label: 'Running' }, + cancelled: { variant: 'gray', label: 'Cancelled' }, + info: { variant: 'gray', label: 'Info' }, } -/** - * Converts a hex color to a background variant with appropriate opacity - */ -export function hexToBackground(hex: string): string { - const r = Number.parseInt(hex.slice(1, 3), 16) - const g = Number.parseInt(hex.slice(3, 5), 16) - const b = Number.parseInt(hex.slice(5, 7), 16) - return `rgba(${r}, ${g}, ${b}, 0.2)` -} - -/** - * Lightens a hex color to make it more vibrant for text - */ -export function lightenColor(hex: string, percent = 30): string { - const r = Number.parseInt(hex.slice(1, 3), 16) - const g = Number.parseInt(hex.slice(3, 5), 16) - const b = Number.parseInt(hex.slice(5, 7), 16) - - const newR = Math.min(255, Math.round(r + (255 - r) * (percent / 100))) - const newG = Math.min(255, Math.round(g + (255 - g) * (percent / 100))) - const newB = Math.min(255, Math.round(b + (255 - b) * (percent / 100))) - - return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}` +/** Configuration mapping core trigger types to Badge color variants */ +const TRIGGER_VARIANT_MAP: Record['variant']> = { + manual: 'gray-secondary', + api: 'blue', + schedule: 'teal', + chat: 'purple', + webhook: 'orange', } interface StatusBadgeProps { + /** The execution status to display */ status: LogStatus } /** - * Displays a styled badge for a log execution status + * Renders a colored badge indicating log execution status. + * @param props - Component props containing the status + * @returns A Badge with dot indicator and status label */ export const StatusBadge = React.memo(({ status }: StatusBadgeProps) => { - const config = { - error: { - bg: 'var(--terminal-status-error-bg)', - color: 'var(--text-error)', - label: 'Error', - }, - pending: { - bg: hexToBackground(PENDING_COLOR), - color: lightenColor(PENDING_COLOR, 65), - label: 'Pending', - }, - running: { - bg: hexToBackground(RUNNING_COLOR), - color: lightenColor(RUNNING_COLOR, 65), - label: 'Running', - }, - cancelled: { - bg: 'var(--terminal-status-info-bg)', - color: 'var(--terminal-status-info-color)', - label: 'Cancelled', - }, - info: { - bg: 'var(--terminal-status-info-bg)', - color: 'var(--terminal-status-info-color)', - label: 'Info', - }, - }[status] - - return React.createElement( - 'div', - { - className: - 'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]', - style: { backgroundColor: config.bg, color: config.color }, - }, - React.createElement('div', { - className: 'h-[6px] w-[6px] rounded-[2px]', - style: { backgroundColor: config.color }, - }), - config.label - ) + const config = STATUS_VARIANT_MAP[status] + return React.createElement(Badge, { variant: config.variant, dot: true }, config.label) }) StatusBadge.displayName = 'StatusBadge' interface TriggerBadgeProps { + /** The trigger type identifier (e.g., 'manual', 'api', or integration block type) */ trigger: string } /** - * Displays a styled badge for a workflow trigger type + * Renders a colored badge indicating the workflow trigger type. + * Core triggers display with their designated colors; integrations show with icons. + * @param props - Component props containing the trigger type + * @returns A Badge with appropriate styling for the trigger type */ export const TriggerBadge = React.memo(({ trigger }: TriggerBadgeProps) => { const metadata = getIntegrationMetadata(trigger) @@ -133,37 +84,20 @@ export const TriggerBadge = React.memo(({ trigger }: TriggerBadgeProps) => { const block = isIntegration ? getBlock(trigger) : null const IconComponent = block?.icon - const isUnknownIntegration = isIntegration && trigger !== 'generic' && !block - if ( - trigger === 'manual' || - trigger === 'generic' || - isUnknownIntegration || - isGrayOrNeutral(metadata.color) - ) { + const coreVariant = TRIGGER_VARIANT_MAP[trigger] + if (coreVariant) { + return React.createElement(Badge, { variant: coreVariant }, metadata.label) + } + + if (IconComponent) { return React.createElement( Badge, - { - variant: 'default', - className: - 'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]', - }, - IconComponent && React.createElement(IconComponent, { className: 'h-[12px] w-[12px]' }), + { variant: 'gray-secondary', icon: IconComponent }, metadata.label ) } - const textColor = lightenColor(metadata.color, 65) - - return React.createElement( - 'div', - { - className: - 'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]', - style: { backgroundColor: hexToBackground(metadata.color), color: textColor }, - }, - IconComponent && React.createElement(IconComponent, { className: 'h-[12px] w-[12px]' }), - metadata.label - ) + return React.createElement(Badge, { variant: 'gray-secondary' }, metadata.label) }) TriggerBadge.displayName = 'TriggerBadge' diff --git a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx index 43c196f2d6..e4cbb443df 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/global-commands-provider.tsx @@ -9,16 +9,11 @@ import { useMemo, useRef, } from 'react' +import { createLogger } from '@sim/logger' import { useRouter } from 'next/navigation' -import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('GlobalCommands') -/** - * Detects if the current platform is macOS. - * - * @returns True if running on macOS, false otherwise - */ function isMacPlatform(): boolean { if (typeof window === 'undefined') return false return ( @@ -27,18 +22,6 @@ function isMacPlatform(): boolean { ) } -/** - * Represents a parsed keyboard shortcut. - * - * We support the following modifiers: - * - Mod: maps to Meta on macOS, Ctrl on other platforms - * - Ctrl, Meta, Shift, Alt - * - * Examples: - * - "Mod+A" - * - "Mod+Shift+T" - * - "Meta+K" - */ export interface ParsedShortcut { key: string mod?: boolean @@ -48,24 +31,10 @@ export interface ParsedShortcut { alt?: boolean } -/** - * Declarative command registration. - */ export interface GlobalCommand { - /** Unique id for the command. If omitted, one is generated. */ id?: string - /** Shortcut string in the form "Mod+Shift+T", "Mod+A", "Meta+K", etc. */ shortcut: string - /** - * Whether to allow the command to run inside editable elements like inputs, - * textareas or contenteditable. Defaults to true to ensure browser defaults - * are overridden when desired. - */ allowInEditable?: boolean - /** - * Handler invoked when the shortcut is matched. Use this to trigger actions - * like navigation or dispatching application events. - */ handler: (event: KeyboardEvent) => void } @@ -80,16 +49,13 @@ interface GlobalCommandsContextValue { const GlobalCommandsContext = createContext(null) -/** - * Parses a human-readable shortcut into a structured representation. - */ function parseShortcut(shortcut: string): ParsedShortcut { const parts = shortcut.split('+').map((p) => p.trim()) const modifiers = new Set(parts.slice(0, -1).map((p) => p.toLowerCase())) const last = parts[parts.length - 1] return { - key: last.length === 1 ? last.toLowerCase() : last, // keep non-letter keys verbatim + key: last.length === 1 ? last.toLowerCase() : last, mod: modifiers.has('mod'), ctrl: modifiers.has('ctrl'), meta: modifiers.has('meta') || modifiers.has('cmd') || modifiers.has('command'), @@ -98,16 +64,10 @@ function parseShortcut(shortcut: string): ParsedShortcut { } } -/** - * Checks if a KeyboardEvent matches a parsed shortcut, honoring platform-specific - * interpretation of "Mod" (Meta on macOS, Ctrl elsewhere). - */ function matchesShortcut(e: KeyboardEvent, parsed: ParsedShortcut): boolean { const isMac = isMacPlatform() const expectedCtrl = parsed.ctrl || (parsed.mod ? !isMac : false) const expectedMeta = parsed.meta || (parsed.mod ? isMac : false) - - // Normalize key for comparison: for letters compare lowercase const eventKey = e.key.length === 1 ? e.key.toLowerCase() : e.key return ( @@ -119,10 +79,6 @@ function matchesShortcut(e: KeyboardEvent, parsed: ParsedShortcut): boolean { ) } -/** - * Provider that captures global keyboard shortcuts and routes them to - * registered commands. Commands can be registered from any descendant component. - */ export function GlobalCommandsProvider({ children }: { children: ReactNode }) { const registryRef = useRef>(new Map()) const isMac = useMemo(() => isMacPlatform(), []) @@ -140,13 +96,11 @@ export function GlobalCommandsProvider({ children }: { children: ReactNode }) { allowInEditable: cmd.allowInEditable ?? true, }) createdIds.push(id) - logger.info('Registered global command', { id, shortcut: cmd.shortcut }) } return () => { for (const id of createdIds) { registryRef.current.delete(id) - logger.info('Unregistered global command', { id }) } } }, []) @@ -155,8 +109,6 @@ export function GlobalCommandsProvider({ children }: { children: ReactNode }) { const onKeyDown = (e: KeyboardEvent) => { if (e.isComposing) return - // Evaluate matches in registration order (latest registration wins naturally - // due to replacement on same id). Break on first match. for (const [, cmd] of registryRef.current) { if (!cmd.allowInEditable) { const ae = document.activeElement @@ -168,16 +120,8 @@ export function GlobalCommandsProvider({ children }: { children: ReactNode }) { } if (matchesShortcut(e, cmd.parsed)) { - // Always override default browser behavior for matched commands. e.preventDefault() e.stopPropagation() - logger.info('Executing global command', { - id: cmd.id, - shortcut: cmd.shortcut, - key: e.key, - isMac, - path: typeof window !== 'undefined' ? window.location.pathname : undefined, - }) try { cmd.handler(e) } catch (err) { @@ -197,22 +141,28 @@ export function GlobalCommandsProvider({ children }: { children: ReactNode }) { return {children} } -/** - * Registers a set of global commands for the lifetime of the component. - * - * Returns nothing; cleanup is automatic on unmount. - */ export function useRegisterGlobalCommands(commands: GlobalCommand[] | (() => GlobalCommand[])) { const ctx = useContext(GlobalCommandsContext) if (!ctx) { throw new Error('useRegisterGlobalCommands must be used within GlobalCommandsProvider') } + const commandsRef = useRef([]) + const list = typeof commands === 'function' ? commands() : commands + commandsRef.current = list + useEffect(() => { - const list = typeof commands === 'function' ? commands() : commands - const unregister = ctx.register(list) + const wrappedCommands = commandsRef.current.map((cmd) => ({ + ...cmd, + handler: (event: KeyboardEvent) => { + const currentCmd = commandsRef.current.find((c) => c.id === cmd.id) + if (currentCmd) { + currentCmd.handler(event) + } + }, + })) + const unregister = ctx.register(wrappedCommands) return unregister - // We intentionally want to register once for the given commands // eslint-disable-next-line react-hooks/exhaustive-deps }, []) } diff --git a/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx b/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx index 107633d835..ae7b8a732f 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect } from 'react' -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' import { useProviderModels } from '@/hooks/queries/providers' import { updateOllamaProviderModels, diff --git a/apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx b/apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx index 9115a9e4bd..73e39bf641 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx @@ -2,8 +2,8 @@ import type React from 'react' import { createContext, useContext, useEffect, useMemo, useState } from 'react' +import { createLogger } from '@sim/logger' import { useParams } from 'next/navigation' -import { createLogger } from '@/lib/logs/console/logger' import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { useUserPermissions, type WorkspaceUserPermissions } from '@/hooks/use-user-permissions' import { diff --git a/apps/sim/app/workspace/[workspaceId]/templates/[id]/page.tsx b/apps/sim/app/workspace/[workspaceId]/templates/[id]/page.tsx index 8e67069d8f..261e178f2b 100644 --- a/apps/sim/app/workspace/[workspaceId]/templates/[id]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/templates/[id]/page.tsx @@ -1,11 +1,11 @@ import { db } from '@sim/db' import { templateCreators, templates } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import type { Metadata } from 'next' import { redirect } from 'next/navigation' import { getSession } from '@/lib/auth' import { getBaseUrl } from '@/lib/core/utils/urls' -import { createLogger } from '@/lib/logs/console/logger' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' import TemplateDetails from '@/app/templates/[id]/template' diff --git a/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx b/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx index 5199252d00..3f080de2d8 100644 --- a/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx @@ -1,9 +1,9 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import { Star, User } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { VerifiedBadge } from '@/components/ui/verified-badge' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview' import { getBlock } from '@/blocks/registry' import { useStarTemplate } from '@/hooks/queries/templates' @@ -29,19 +29,19 @@ export function TemplateCardSkeleton({ className }: { className?: string }) { return (
-
+
-
+
{Array.from({ length: 3 }).map((_, index) => (
))}
@@ -49,14 +49,14 @@ export function TemplateCardSkeleton({ className }: { className?: string }) {
-
-
+
+
-
-
-
-
+
+
+
+
@@ -202,7 +202,7 @@ function TemplateCardInner({
@@ -223,12 +223,14 @@ function TemplateCardInner({ cursorStyle='pointer' /> ) : ( -
+
)}
-

{title}

+

+ {title} +

{blockTypes.length > 4 ? ( @@ -251,10 +253,12 @@ function TemplateCardInner({ ) })}
- +{blockTypes.length - 3} + + +{blockTypes.length - 3} +
) : ( @@ -286,24 +290,26 @@ function TemplateCardInner({ {author}
) : ( -
- +
+
)}
- {author} + + {author} + {isVerified && }
-
+
{usageCount} diff --git a/apps/sim/app/workspace/[workspaceId]/templates/templates.tsx b/apps/sim/app/workspace/[workspaceId]/templates/templates.tsx index 54f75ff22d..c92fa5bfc2 100644 --- a/apps/sim/app/workspace/[workspaceId]/templates/templates.tsx +++ b/apps/sim/app/workspace/[workspaceId]/templates/templates.tsx @@ -175,8 +175,8 @@ export default function Templates({
-
- +
+

Templates

@@ -186,7 +186,7 @@ export default function Templates({
-
+
(null) const timeoutRef = useRef(null) const streamReaderRef = useRef | null>(null) + const preventZoomRef = usePreventZoom() // File upload hook const { @@ -814,7 +818,8 @@ export function Chat() { return (
{/* File thumbnails */} @@ -1055,7 +1060,7 @@ export function Chat() { {isStreaming ? ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/chat-message/chat-message.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/chat-message/chat-message.tsx index ae9c5759ee..a669650216 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/chat-message/chat-message.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/chat-message/chat-message.tsx @@ -156,7 +156,7 @@ export function ChatMessage({ message }: ChatMessageProps) { )} {formattedContent && !formattedContent.startsWith('Uploaded') && ( -
+
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx index 40ff482445..e8df018422 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx @@ -1,7 +1,8 @@ 'use client' -import { useEffect, useMemo, useRef, useState } from 'react' -import { Check } from 'lucide-react' +import type React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Check, RepeatIcon, SplitIcon } from 'lucide-react' import { Badge, Popover, @@ -19,6 +20,32 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +/** + * Renders a tag icon with background color. + * + * @param icon - Either a letter string or a Lucide icon component + * @param color - Background color for the icon container + * @returns A styled icon element + */ +const TagIcon: React.FC<{ + icon: string | React.ComponentType<{ className?: string }> + color: string +}> = ({ icon, color }) => ( +
+ {typeof icon === 'string' ? ( + {icon} + ) : ( + (() => { + const IconComponent = icon + return + })() + )} +
+) + /** * Props for the OutputSelect component */ @@ -71,7 +98,6 @@ export function OutputSelect({ const [highlightedIndex, setHighlightedIndex] = useState(-1) const triggerRef = useRef(null) const popoverRef = useRef(null) - const contentRef = useRef(null) const blocks = useWorkflowStore((state) => state.blocks) const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore() const subBlockValues = useSubBlockStore((state) => @@ -185,8 +211,11 @@ export function OutputSelect({ * @param o - The output object to check * @returns True if the output is selected, false otherwise */ - const isSelectedValue = (o: { id: string; label: string }) => - selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label) + const isSelectedValue = useCallback( + (o: { id: string; label: string }) => + selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label), + [selectedOutputs] + ) /** * Gets display text for selected outputs @@ -292,82 +321,94 @@ export function OutputSelect({ * Handles output selection by toggling the selected state * @param value - The output label to toggle */ - const handleOutputSelection = (value: string) => { - const emittedValue = - valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value - const index = selectedOutputs.indexOf(emittedValue) - - const newSelectedOutputs = - index === -1 - ? [...new Set([...selectedOutputs, emittedValue])] - : selectedOutputs.filter((id) => id !== emittedValue) - - onOutputSelect(newSelectedOutputs) - } + const handleOutputSelection = useCallback( + (value: string) => { + const emittedValue = + valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value + const index = selectedOutputs.indexOf(emittedValue) + + const newSelectedOutputs = + index === -1 + ? [...new Set([...selectedOutputs, emittedValue])] + : selectedOutputs.filter((id) => id !== emittedValue) + + onOutputSelect(newSelectedOutputs) + }, + [valueMode, workflowOutputs, selectedOutputs, onOutputSelect] + ) /** * Handles keyboard navigation within the output list * Supports ArrowUp, ArrowDown, Enter, and Escape keys - * @param e - Keyboard event */ - const handleKeyDown = (e: React.KeyboardEvent) => { - if (flattenedOutputs.length === 0) return - - switch (e.key) { - case 'ArrowDown': - e.preventDefault() - setHighlightedIndex((prev) => { - const next = prev < flattenedOutputs.length - 1 ? prev + 1 : 0 - return next - }) - break - - case 'ArrowUp': - e.preventDefault() - setHighlightedIndex((prev) => { - const next = prev > 0 ? prev - 1 : flattenedOutputs.length - 1 - return next - }) - break - - case 'Enter': - e.preventDefault() - if (highlightedIndex >= 0 && highlightedIndex < flattenedOutputs.length) { - handleOutputSelection(flattenedOutputs[highlightedIndex].label) - } - break + useEffect(() => { + if (!open || flattenedOutputs.length === 0) return + + const handleKeyboardEvent = (e: KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': + e.preventDefault() + e.stopPropagation() + setHighlightedIndex((prev) => { + if (prev === -1 || prev >= flattenedOutputs.length - 1) { + return 0 + } + return prev + 1 + }) + break + + case 'ArrowUp': + e.preventDefault() + e.stopPropagation() + setHighlightedIndex((prev) => { + if (prev <= 0) { + return flattenedOutputs.length - 1 + } + return prev - 1 + }) + break + + case 'Enter': + e.preventDefault() + e.stopPropagation() + setHighlightedIndex((currentIndex) => { + if (currentIndex >= 0 && currentIndex < flattenedOutputs.length) { + handleOutputSelection(flattenedOutputs[currentIndex].label) + } + return currentIndex + }) + break - case 'Escape': - e.preventDefault() - setOpen(false) - break + case 'Escape': + e.preventDefault() + e.stopPropagation() + setOpen(false) + break + } } - } + + window.addEventListener('keydown', handleKeyboardEvent, true) + return () => window.removeEventListener('keydown', handleKeyboardEvent, true) + }, [open, flattenedOutputs, handleOutputSelection]) /** * Reset highlighted index when popover opens/closes */ useEffect(() => { if (open) { - // Find first selected item, or start at -1 const firstSelectedIndex = flattenedOutputs.findIndex((output) => isSelectedValue(output)) setHighlightedIndex(firstSelectedIndex >= 0 ? firstSelectedIndex : -1) - - // Focus the content for keyboard navigation - setTimeout(() => { - contentRef.current?.focus() - }, 0) } else { setHighlightedIndex(-1) } - }, [open, flattenedOutputs]) + }, [open, flattenedOutputs, isSelectedValue]) /** * Scroll highlighted item into view */ useEffect(() => { - if (highlightedIndex >= 0 && contentRef.current) { - const highlightedElement = contentRef.current.querySelector( + if (highlightedIndex >= 0 && popoverRef.current) { + const highlightedElement = popoverRef.current.querySelector( `[data-option-index="${highlightedIndex}"]` ) if (highlightedElement) { @@ -425,18 +466,35 @@ export function OutputSelect({ minWidth={160} border disablePortal={disablePopoverPortal} - onKeyDown={handleKeyDown} - tabIndex={0} - style={{ outline: 'none' }} > -
+
{Object.entries(groupedOutputs).map(([blockName, outputs]) => { - // Calculate the starting index for this group const startIndex = flattenedOutputs.findIndex((o) => o.blockName === blockName) + const firstOutput = outputs[0] + const blockConfig = getBlock(firstOutput.blockType) + const blockColor = getOutputColor(firstOutput.blockId, firstOutput.blockType) + + let blockIcon: string | React.ComponentType<{ className?: string }> = blockName + .charAt(0) + .toUpperCase() + + if (blockConfig?.icon) { + blockIcon = blockConfig.icon + } else if (firstOutput.blockType === 'loop') { + blockIcon = RepeatIcon + } else if (firstOutput.blockType === 'parallel') { + blockIcon = SplitIcon + } + return (
- {blockName} + +
+ + {blockName} +
+
{outputs.map((output, localIndex) => { @@ -451,17 +509,9 @@ export function OutputSelect({ onClick={() => handleOutputSelection(output.label)} onMouseEnter={() => setHighlightedIndex(globalIndex)} > -
- - {blockName.charAt(0).toUpperCase()} - -
- {output.path} + + {output.path} + {isSelectedValue(output) && } ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx index 22ccc5b9ae..4bf50085c5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/command-list/command-list.tsx @@ -1,13 +1,14 @@ 'use client' import { useCallback } from 'react' +import { createLogger } from '@sim/logger' import { Layout, LibraryBig, Search } from 'lucide-react' import Image from 'next/image' import { useParams, useRouter } from 'next/navigation' import { Button } from '@/components/emcn' import { AgentIcon } from '@/components/icons' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' +import { usePreventZoom } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks' import { useSearchModalStore } from '@/stores/search-modal/store' const logger = createLogger('WorkflowCommandList') @@ -58,6 +59,7 @@ export function CommandList() { const params = useParams() const router = useRouter() const { open: openSearchModal } = useSearchModalStore() + const preventZoomRef = usePreventZoom() const workspaceId = params.workspaceId as string | undefined @@ -171,6 +173,7 @@ export function CommandList() { return (
{ const viewport = useViewport() const session = useSession() const currentUserId = session.data?.user?.id + const preventZoomRef = usePreventZoom() const cursors = useMemo(() => { return presenceUsers @@ -41,7 +43,7 @@ const CursorsComponent = () => { } return ( -
+
{cursors.map(({ id, name, cursor, color }) => { const x = cursor.x * viewport.zoom + viewport.x const y = cursor.y * viewport.zoom + viewport.y diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx index 570b2edc04..5c3dbaebf6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx @@ -1,8 +1,9 @@ import { memo, useCallback } from 'react' +import { createLogger } from '@sim/logger' import clsx from 'clsx' import { Eye, EyeOff } from 'lucide-react' import { Button } from '@/components/emcn' -import { createLogger } from '@/lib/logs/console/logger' +import { usePreventZoom } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks' import { useCopilotStore } from '@/stores/panel/copilot/store' import { useTerminalStore } from '@/stores/terminal' import { useWorkflowDiffStore } from '@/stores/workflow-diff' @@ -14,7 +15,6 @@ const logger = createLogger('DiffControls') export const DiffControls = memo(function DiffControls() { const isTerminalResizing = useTerminalStore((state) => state.isResizing) - // Optimized: Single diff store subscription const { isShowingDiff, isDiffReady, @@ -38,12 +38,10 @@ export const DiffControls = memo(function DiffControls() { ) ) - // Optimized: Single copilot store subscription for needed values - const { updatePreviewToolCallState, clearPreviewYaml, currentChat, messages } = useCopilotStore( + const { updatePreviewToolCallState, currentChat, messages } = useCopilotStore( useCallback( (state) => ({ updatePreviewToolCallState: state.updatePreviewToolCallState, - clearPreviewYaml: state.clearPreviewYaml, currentChat: state.currentChat, messages: state.messages, }), @@ -222,11 +220,6 @@ export const DiffControls = memo(function DiffControls() { logger.warn('Failed to create checkpoint before accept:', error) }) - // Clear preview YAML immediately - await clearPreviewYaml().catch((error) => { - logger.warn('Failed to clear preview YAML:', error) - }) - // Resolve target toolCallId for build/edit and update to terminal success state in the copilot store try { const { toolCallsById, messages } = useCopilotStore.getState() @@ -266,16 +259,11 @@ export const DiffControls = memo(function DiffControls() { logger.error('Workflow update failed:', errorMessage) alert(`Failed to save workflow changes: ${errorMessage}`) } - }, [createCheckpoint, clearPreviewYaml, updatePreviewToolCallState, acceptChanges]) + }, [createCheckpoint, updatePreviewToolCallState, acceptChanges]) const handleReject = useCallback(() => { logger.info('Rejecting proposed changes (optimistic)') - // Clear preview YAML immediately - clearPreviewYaml().catch((error) => { - logger.warn('Failed to clear preview YAML:', error) - }) - // Resolve target toolCallId for build/edit and update to terminal rejected state in the copilot store try { const { toolCallsById, messages } = useCopilotStore.getState() @@ -306,7 +294,9 @@ export const DiffControls = memo(function DiffControls() { rejectChanges().catch((error) => { logger.error('Failed to reject changes (background):', error) }) - }, [clearPreviewYaml, updatePreviewToolCallState, rejectChanges]) + }, [updatePreviewToolCallState, rejectChanges]) + + const preventZoomRef = usePreventZoom() // Don't show anything if no diff is available or diff is not ready if (!hasActiveDiff || !isDiffReady) { @@ -315,6 +305,7 @@ export const DiffControls = memo(function DiffControls() { return (
Accept diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx index fb7f292697..1b7866796e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx @@ -1,8 +1,10 @@ 'use client' import { Component, type ReactNode, useEffect } from 'react' -import { createLogger } from '@/lib/logs/console/logger' +import { createLogger } from '@sim/logger' +import { ReactFlowProvider } from 'reactflow' import { Panel } from '@/app/workspace/[workspaceId]/w/[workflowId]/components' +import { usePreventZoom } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks' import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar' const logger = createLogger('ErrorBoundary') @@ -23,12 +25,13 @@ export function ErrorUI({ onReset, fullScreen = false, }: ErrorUIProps) { + const preventZoomRef = usePreventZoom() const containerClass = fullScreen ? 'flex flex-col w-full h-screen bg-[var(--surface-1)]' : 'flex flex-col w-full h-full bg-[var(--surface-1)]' return ( -
+
{/* Sidebar */} @@ -47,8 +50,9 @@ export function ErrorUI({
- {/* Panel */} - + + +
) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts index 948fc180cf..123cab3b1a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts @@ -4,7 +4,6 @@ export { DiffControls } from './diff-controls/diff-controls' export { ErrorBoundary } from './error/index' export { Notifications } from './notifications/notifications' export { Panel } from './panel/panel' -export { SkeletonLoading } from './skeleton-loading/skeleton-loading' export { SubflowNodeComponent } from './subflows/subflow-node' export { Terminal } from './terminal/terminal' export { WandPromptBar } from './wand-prompt-bar/wand-prompt-bar' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx index 4ca430d854..655cf84f9b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx @@ -27,6 +27,21 @@ function extractFieldValue(rawValue: unknown): string | undefined { return undefined } +/** + * Extract YouTube video ID from various YouTube URL formats + */ +function getYouTubeVideoId(url: string): string | null { + const patterns = [ + /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/, + /youtube\.com\/watch\?.*v=([a-zA-Z0-9_-]{11})/, + ] + for (const pattern of patterns) { + const match = url.match(pattern) + if (match) return match[1] + } + return null +} + /** * Compact markdown renderer for note blocks with tight spacing */ @@ -36,39 +51,41 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string } remarkPlugins={[remarkGfm]} components={{ p: ({ children }: any) => ( -

{children}

+

+ {children} +

), h1: ({ children }: any) => ( -

+

{children}

), h2: ({ children }: any) => ( -

+

{children}

), h3: ({ children }: any) => ( -

+

{children}

), h4: ({ children }: any) => ( -

+

{children}

), ul: ({ children }: any) => ( -
    +
      {children}
    ), ol: ({ children }: any) => ( -
      +
        {children}
      ), - li: ({ children }: any) =>
    1. {children}
    2. , + li: ({ children }: any) =>
    3. {children}
    4. , code: ({ inline, className, children, ...props }: any) => { const isInline = inline || !className?.includes('language-') @@ -76,7 +93,7 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string } return ( {children} @@ -92,22 +109,51 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string } ) }, - a: ({ href, children }: any) => ( - - {children} - - ), + a: ({ href, children }: any) => { + const videoId = href ? getYouTubeVideoId(href) : null + if (videoId) { + return ( + + + {children} + + +