fix: use withTenantDb for skills API routes (RLS fix) and seed default skills
All checks were successful
Deploy to VPS / deploy (push) Successful in 40s
All checks were successful
Deploy to VPS / deploy (push) Successful in 40s
All skills API routes were using `db` directly instead of `withTenantDb`, causing RLS to block all operations since `app.tenant_id` was never set. This caused "Netzwerkfehler" when creating/reading skills. Also fixes the broken seed migration (0005) which referenced a non-existent column in the CROSS JOIN, preventing default system skills from being inserted. New migration 0006 properly seeds the 4 default skills with full system prompts. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
58
drizzle/0006_seed_system_skills_fix.sql
Normal file
58
drizzle/0006_seed_system_skills_fix.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- Fix: properly seed system skills with full system prompts
|
||||
-- The original migration (0005) had a broken CROSS JOIN that failed to insert skills.
|
||||
-- This migration uses individual INSERTs per tenant to avoid the issue.
|
||||
|
||||
-- Base instructions shared by all skills
|
||||
-- (embedded directly in each prompt below)
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
tenant_record RECORD;
|
||||
base_prompt TEXT := E'Du bist ein juristischer Assistent für deutsches Bühnenrecht (Theaterrecht).\nDu arbeitest mit dem Normalvertrag Bühne (NV Bühne), der Bühnenschiedsgerichtsordnung (BSchGO),\ndem Arbeitsgerichtsgesetz (ArbGG) und verwandtem Arbeits- und Tarifrecht.\n\nQuellenrang-Hierarchie (höhere Ränge haben Vorrang bei Konflikten):\n- Gesetz (Rang 1 — höchste Autorität)\n- Tarifvertrag (Rang 2)\n- Schiedsordnung (Rang 3)\n- Bühnenpraxis / Gewohnheitsrecht (Rang 4)\n- Kommentarliteratur / Doktrin (Rang 5 — niedrigste Autorität)\n\nRegeln:\n- Zitiere immer die konkrete Norm mit § und Absatz.\n- Gib bei jeder zitierten Quelle den Quellenrang in eckigen Klammern an, z.B. [Rang 1: Gesetz].\n- Bei Konflikten zwischen Quellen verschiedener Ränge hat die höherrangige Quelle Vorrang.\n- Antworte ausschließlich auf Deutsch.\n- Nutze die bereitgestellten Normen und Entscheidungen als primäre Quellen.';
|
||||
BEGIN
|
||||
FOR tenant_record IN SELECT id FROM tenants LOOP
|
||||
-- Gutachten
|
||||
INSERT INTO skills (tenant_id, slug, name, description, system_prompt, output_type, requires_norms, requires_decisions, is_system, sort_order, is_active)
|
||||
VALUES (
|
||||
tenant_record.id,
|
||||
'gutachten',
|
||||
'Rechtsgutachten',
|
||||
'Strukturiertes Gutachten nach klassischer Methodik (Obersatz → Definition → Subsumtion → Ergebnis)',
|
||||
base_prompt || E'\n\nModus: GUTACHTEN (Rechtsgutachten)\n\nErstelle ein strukturiertes Rechtsgutachten nach der klassischen Methodik:\n\n1. **Sachverhalt** — Kurze Zusammenfassung des zu prüfenden Sachverhalts\n2. **Rechtsfrage** — Präzise Formulierung der zu klärenden Rechtsfrage(n)\n3. **Obersatz** — Abstrakte Rechtsregel aus der einschlägigen Norm\n4. **Definition** — Auslegung der relevanten Tatbestandsmerkmale\n5. **Untersatz** — Subsumtion des Sachverhalts unter die Norm\n6. **Ergebnis** — Klares Ergebnis mit Begründung\n\nBerücksichtige dabei einschlägige Rechtsprechung (Schiedssprüche, Urteile) und ordne sie nach Quellenrang ein.',
|
||||
'analysis', true, true, true, 0, true
|
||||
) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Entscheidungsvorhersage
|
||||
INSERT INTO skills (tenant_id, slug, name, description, system_prompt, output_type, requires_norms, requires_decisions, is_system, sort_order, is_active)
|
||||
VALUES (
|
||||
tenant_record.id,
|
||||
'entscheidung',
|
||||
'Entscheidungsvorhersage',
|
||||
'Prognose der wahrscheinlichen gerichtlichen/schiedsgerichtlichen Entscheidung',
|
||||
base_prompt || E'\n\nModus: ENTSCHEIDUNG (Entscheidungsvorhersage)\n\nAnalysiere den Sachverhalt und prognostiziere die wahrscheinliche Entscheidung:\n\n1. **Sachverhalt** — Zusammenfassung der relevanten Tatsachen\n2. **Einschlägige Normen** — Anwendbare Vorschriften mit Quellenrang\n3. **Bisherige Rechtsprechung** — Relevante Präzedenzfälle und deren Entscheidungslinien\n4. **Prognose** — Wahrscheinlichste Entscheidung mit Begründung\n5. **Risikofaktoren** — Faktoren, die das Ergebnis beeinflussen könnten\n6. **Empfehlung** — Handlungsempfehlung für den Mandanten\n\nStütze die Prognose auf konkrete Entscheidungen und deren Leitsätze.',
|
||||
'analysis', true, true, true, 1, true
|
||||
) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Vergleichsvorschlag
|
||||
INSERT INTO skills (tenant_id, slug, name, description, system_prompt, output_type, requires_norms, requires_decisions, is_system, sort_order, is_active)
|
||||
VALUES (
|
||||
tenant_record.id,
|
||||
'vergleich',
|
||||
'Vergleichsvorschlag',
|
||||
'Erarbeitung eines Vergleichsvorschlags mit Bewertung der Erfolgsaussichten',
|
||||
base_prompt || E'\n\nModus: VERGLEICH (Vergleichsvorschlag)\n\nErarbeite einen Vergleichsvorschlag:\n\n1. **Ausgangslage** — Positionen beider Parteien\n2. **Rechtslage** — Einschlägige Normen und deren Wertung\n3. **Erfolgsaussichten** — Prozentuale Einschätzung für jede Partei (mit Begründung)\n4. **Vergleichsvorschlag** — Konkreter Kompromissvorschlag\n5. **Vor-/Nachteile** — Bewertung des Vorschlags für beide Seiten\n6. **Umsetzung** — Praktische Schritte zur Umsetzung\n\nBeziehe die wirtschaftlichen Interessen beider Seiten ein (Kosten, Zeit, Reputation).',
|
||||
'analysis', true, false, true, 2, true
|
||||
) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Risikoanalyse
|
||||
INSERT INTO skills (tenant_id, slug, name, description, system_prompt, output_type, requires_norms, requires_decisions, is_system, sort_order, is_active)
|
||||
VALUES (
|
||||
tenant_record.id,
|
||||
'risiko',
|
||||
'Risikoanalyse',
|
||||
'Umfassende Risikoanalyse mit Eintrittswahrscheinlichkeiten und Minderungsstrategien',
|
||||
base_prompt || E'\n\nModus: RISIKO (Risikoanalyse)\n\nErstelle eine umfassende Risikoanalyse:\n\n1. **Sachverhalt** — Zusammenfassung der Situation\n2. **Identifizierte Risiken** — Auflistung aller rechtlichen Risiken, jeweils mit:\n - Beschreibung des Risikos\n - Eintrittswahrscheinlichkeit (hoch/mittel/gering)\n - Schadensausmaß (hoch/mittel/gering)\n - Einschlägige Norm(en) mit Quellenrang\n3. **Risikomatrix** — Tabellarische Übersicht (Wahrscheinlichkeit × Auswirkung)\n4. **Minderungsstrategien** — Konkrete Maßnahmen je Risiko\n5. **Priorisierung** — Dringlichste Handlungsempfehlungen\n\nBewerte jedes Risiko anhand der aktuellen Rechtslage und Rechtsprechung.',
|
||||
'analysis', true, true, true, 3, true
|
||||
) ON CONFLICT DO NOTHING;
|
||||
END LOOP;
|
||||
END $$;
|
||||
@@ -43,6 +43,13 @@
|
||||
"when": 1776364800000,
|
||||
"tag": "0005_skills_and_analysis_refactor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1776451200000,
|
||||
"tag": "0006_seed_system_skills_fix",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,17 +2,19 @@
|
||||
// PATCH /api/settings/skills/[id] — Update a skill
|
||||
// DELETE /api/settings/skills/[id] — Soft-delete (set isActive = false)
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { withTenantDb } from '@/lib/db';
|
||||
import { skills } from '@/lib/db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { requirePermission } from '@/lib/auth/rbac';
|
||||
|
||||
async function findSkillForTenant(skillId: string, tenantId: string) {
|
||||
const [skill] = await db
|
||||
.select()
|
||||
.from(skills)
|
||||
.where(and(eq(skills.id, skillId), eq(skills.tenantId, tenantId)))
|
||||
.limit(1);
|
||||
const [skill] = await withTenantDb(tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select()
|
||||
.from(skills)
|
||||
.where(eq(skills.id, skillId))
|
||||
.limit(1),
|
||||
);
|
||||
return skill ?? null;
|
||||
}
|
||||
|
||||
@@ -70,11 +72,13 @@ export async function PATCH(
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const existing = await db
|
||||
.select({ id: skills.id })
|
||||
.from(skills)
|
||||
.where(and(eq(skills.tenantId, ctx.tenantId), eq(skills.slug, slug)))
|
||||
.limit(1);
|
||||
const existing = await withTenantDb(ctx.tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select({ id: skills.id })
|
||||
.from(skills)
|
||||
.where(eq(skills.slug, slug))
|
||||
.limit(1),
|
||||
);
|
||||
if (existing.length > 0 && existing[0].id !== id) {
|
||||
return Response.json(
|
||||
{ error: 'Ein Skill mit diesem Slug existiert bereits.' },
|
||||
@@ -101,11 +105,13 @@ export async function PATCH(
|
||||
if (requiresDecisions !== undefined) updates.requiresDecisions = requiresDecisions;
|
||||
if (isActive !== undefined) updates.isActive = isActive;
|
||||
|
||||
const [updated] = await db
|
||||
.update(skills)
|
||||
.set(updates)
|
||||
.where(eq(skills.id, id))
|
||||
.returning();
|
||||
const [updated] = await withTenantDb(ctx.tenantId, async (tdb) =>
|
||||
tdb
|
||||
.update(skills)
|
||||
.set(updates)
|
||||
.where(eq(skills.id, id))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
return Response.json(updated);
|
||||
}
|
||||
@@ -132,11 +138,13 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
// Soft-delete: set isActive = false
|
||||
const [updated] = await db
|
||||
.update(skills)
|
||||
.set({ isActive: false, updatedAt: new Date() })
|
||||
.where(eq(skills.id, id))
|
||||
.returning();
|
||||
const [updated] = await withTenantDb(ctx.tenantId, async (tdb) =>
|
||||
tdb
|
||||
.update(skills)
|
||||
.set({ isActive: false, updatedAt: new Date() })
|
||||
.where(eq(skills.id, id))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
return Response.json(updated);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// PATCH /api/settings/skills/reorder — Update sort_order for drag-and-drop reordering
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { withTenantDb } from '@/lib/db';
|
||||
import { skills } from '@/lib/db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requirePermission } from '@/lib/auth/rbac';
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
@@ -19,14 +19,14 @@ export async function PATCH(request: Request) {
|
||||
return Response.json({ error: 'order Array ist erforderlich.' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Update each skill's sortOrder within a transaction-like loop
|
||||
// Verify tenant ownership for each skill
|
||||
for (const item of order) {
|
||||
await db
|
||||
.update(skills)
|
||||
.set({ sortOrder: item.sortOrder, updatedAt: new Date() })
|
||||
.where(and(eq(skills.id, item.id), eq(skills.tenantId, ctx.tenantId)));
|
||||
}
|
||||
await withTenantDb(ctx.tenantId, async (tdb) => {
|
||||
for (const item of order) {
|
||||
await tdb
|
||||
.update(skills)
|
||||
.set({ sortOrder: item.sortOrder, updatedAt: new Date() })
|
||||
.where(eq(skills.id, item.id));
|
||||
}
|
||||
});
|
||||
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// GET /api/settings/skills — List all skills for tenant (sorted by sortOrder)
|
||||
// POST /api/settings/skills — Create a new skill
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { withTenantDb } from '@/lib/db';
|
||||
import { skills } from '@/lib/db/schema';
|
||||
import { eq, and, asc } from 'drizzle-orm';
|
||||
import { requirePermission } from '@/lib/auth/rbac';
|
||||
@@ -10,11 +10,12 @@ export async function GET() {
|
||||
const auth = await requirePermission('settings:manage');
|
||||
if ('response' in auth) return auth.response;
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(skills)
|
||||
.where(eq(skills.tenantId, auth.ctx.tenantId))
|
||||
.orderBy(asc(skills.sortOrder), asc(skills.createdAt));
|
||||
const rows = await withTenantDb(auth.ctx.tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select()
|
||||
.from(skills)
|
||||
.orderBy(asc(skills.sortOrder), asc(skills.createdAt)),
|
||||
);
|
||||
|
||||
return Response.json(rows);
|
||||
}
|
||||
@@ -58,44 +59,51 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
// Check slug uniqueness within tenant
|
||||
const existing = await db
|
||||
.select({ id: skills.id })
|
||||
.from(skills)
|
||||
.where(and(eq(skills.tenantId, ctx.tenantId), eq(skills.slug, slug)))
|
||||
.limit(1);
|
||||
const created = await withTenantDb(ctx.tenantId, async (tdb) => {
|
||||
// Check slug uniqueness within tenant
|
||||
const existing = await tdb
|
||||
.select({ id: skills.id })
|
||||
.from(skills)
|
||||
.where(eq(skills.slug, slug))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
if (existing.length > 0) {
|
||||
return null; // slug conflict
|
||||
}
|
||||
|
||||
// Get max sort order for positioning
|
||||
const allSkills = await tdb
|
||||
.select({ sortOrder: skills.sortOrder })
|
||||
.from(skills);
|
||||
const maxOrder = allSkills.reduce((max, s) => Math.max(max, s.sortOrder), -1);
|
||||
|
||||
const [row] = await tdb
|
||||
.insert(skills)
|
||||
.values({
|
||||
tenantId: ctx.tenantId,
|
||||
name,
|
||||
slug,
|
||||
description: description || null,
|
||||
systemPrompt,
|
||||
outputType: (outputType as 'analysis' | 'structured_data') || 'analysis',
|
||||
outputSchema: outputSchema || null,
|
||||
requiresNorms: requiresNorms ?? false,
|
||||
requiresDecisions: requiresDecisions ?? false,
|
||||
isSystem: false,
|
||||
sortOrder: maxOrder + 1,
|
||||
isActive: isActive ?? true,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
if (!created) {
|
||||
return Response.json(
|
||||
{ error: 'Ein Skill mit diesem Slug existiert bereits.' },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
// Get max sort order for positioning
|
||||
const allSkills = await db
|
||||
.select({ sortOrder: skills.sortOrder })
|
||||
.from(skills)
|
||||
.where(eq(skills.tenantId, ctx.tenantId));
|
||||
const maxOrder = allSkills.reduce((max, s) => Math.max(max, s.sortOrder), -1);
|
||||
|
||||
const [created] = await db
|
||||
.insert(skills)
|
||||
.values({
|
||||
tenantId: ctx.tenantId,
|
||||
name,
|
||||
slug,
|
||||
description: description || null,
|
||||
systemPrompt,
|
||||
outputType: (outputType as 'analysis' | 'structured_data') || 'analysis',
|
||||
outputSchema: outputSchema || null,
|
||||
requiresNorms: requiresNorms ?? false,
|
||||
requiresDecisions: requiresDecisions ?? false,
|
||||
isSystem: false,
|
||||
sortOrder: maxOrder + 1,
|
||||
isActive: isActive ?? true,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return Response.json(created, { status: 201 });
|
||||
}
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
// GET /api/skills — List active skills for the current tenant (read-only)
|
||||
// Used by the analyse form to populate the skill selector.
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { withTenantDb } from '@/lib/db';
|
||||
import { skills } from '@/lib/db/schema';
|
||||
import { eq, and, asc } from 'drizzle-orm';
|
||||
import { eq, asc } from 'drizzle-orm';
|
||||
import { requirePermission } from '@/lib/auth/rbac';
|
||||
|
||||
export async function GET() {
|
||||
const auth = await requirePermission('analyses:create');
|
||||
if ('response' in auth) return auth.response;
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
name: skills.name,
|
||||
description: skills.description,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
isSystem: skills.isSystem,
|
||||
sortOrder: skills.sortOrder,
|
||||
})
|
||||
.from(skills)
|
||||
.where(
|
||||
and(
|
||||
eq(skills.tenantId, auth.ctx.tenantId),
|
||||
eq(skills.isActive, true),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(skills.sortOrder), asc(skills.createdAt));
|
||||
const rows = await withTenantDb(auth.ctx.tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select({
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
name: skills.name,
|
||||
description: skills.description,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
isSystem: skills.isSystem,
|
||||
sortOrder: skills.sortOrder,
|
||||
})
|
||||
.from(skills)
|
||||
.where(eq(skills.isActive, true))
|
||||
.orderBy(asc(skills.sortOrder), asc(skills.createdAt)),
|
||||
);
|
||||
|
||||
return Response.json(rows);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildContextBlock } from './prompts';
|
||||
import { SYSTEM_PROMPTS, type AnalysisModeKey } from './prompts';
|
||||
import { ANALYSIS_MODES } from './modes';
|
||||
import { AnalyseMode } from '@/types';
|
||||
import { db } from '@/lib/db';
|
||||
import { db, withTenantDb } from '@/lib/db';
|
||||
import { norms, normInstruments, decisions, analyses, documents, skills } from '@/lib/db/schema';
|
||||
import { eq, and, lte, or, isNull, gte, inArray } from 'drizzle-orm';
|
||||
|
||||
@@ -51,27 +51,25 @@ async function resolveSkill(
|
||||
tenantId: string,
|
||||
input: Pick<AnalysisInput, 'skillId' | 'skillSlug' | 'mode'>,
|
||||
): Promise<ResolvedSkill> {
|
||||
const skillFields = {
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
systemPrompt: skills.systemPrompt,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
};
|
||||
|
||||
// Try by skillId first
|
||||
if (input.skillId) {
|
||||
const [skill] = await db
|
||||
.select({
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
systemPrompt: skills.systemPrompt,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
})
|
||||
.from(skills)
|
||||
.where(
|
||||
and(
|
||||
eq(skills.id, input.skillId),
|
||||
eq(skills.tenantId, tenantId),
|
||||
eq(skills.isActive, true),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
const [skill] = await withTenantDb(tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select(skillFields)
|
||||
.from(skills)
|
||||
.where(and(eq(skills.id, input.skillId!), eq(skills.isActive, true)))
|
||||
.limit(1),
|
||||
);
|
||||
|
||||
if (skill) return skill;
|
||||
throw new Error(`Skill not found: ${input.skillId}`);
|
||||
@@ -79,25 +77,13 @@ async function resolveSkill(
|
||||
|
||||
// Try by skillSlug
|
||||
if (input.skillSlug) {
|
||||
const [skill] = await db
|
||||
.select({
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
systemPrompt: skills.systemPrompt,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
})
|
||||
.from(skills)
|
||||
.where(
|
||||
and(
|
||||
eq(skills.slug, input.skillSlug),
|
||||
eq(skills.tenantId, tenantId),
|
||||
eq(skills.isActive, true),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
const [skill] = await withTenantDb(tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select(skillFields)
|
||||
.from(skills)
|
||||
.where(and(eq(skills.slug, input.skillSlug!), eq(skills.isActive, true)))
|
||||
.limit(1),
|
||||
);
|
||||
|
||||
if (skill) return skill;
|
||||
throw new Error(`Skill not found: ${input.skillSlug}`);
|
||||
@@ -105,25 +91,13 @@ async function resolveSkill(
|
||||
|
||||
// Legacy fallback: resolve mode enum to a DB skill (system skill with matching slug)
|
||||
if (input.mode) {
|
||||
const [skill] = await db
|
||||
.select({
|
||||
id: skills.id,
|
||||
slug: skills.slug,
|
||||
systemPrompt: skills.systemPrompt,
|
||||
outputType: skills.outputType,
|
||||
outputSchema: skills.outputSchema,
|
||||
requiresNorms: skills.requiresNorms,
|
||||
requiresDecisions: skills.requiresDecisions,
|
||||
})
|
||||
.from(skills)
|
||||
.where(
|
||||
and(
|
||||
eq(skills.slug, input.mode),
|
||||
eq(skills.tenantId, tenantId),
|
||||
eq(skills.isActive, true),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
const [skill] = await withTenantDb(tenantId, async (tdb) =>
|
||||
tdb
|
||||
.select(skillFields)
|
||||
.from(skills)
|
||||
.where(and(eq(skills.slug, input.mode!), eq(skills.isActive, true)))
|
||||
.limit(1),
|
||||
);
|
||||
|
||||
if (skill) return skill;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user