stagent 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/cli.js +226 -1
- package/docs/.coverage-gaps.json +66 -16
- package/docs/.last-generated +1 -1
- package/docs/features/dashboard-kanban.md +13 -7
- package/docs/features/settings.md +15 -3
- package/docs/features/tables.md +122 -0
- package/docs/index.md +3 -2
- package/docs/journeys/developer.md +26 -16
- package/docs/journeys/personal-use.md +23 -9
- package/docs/journeys/power-user.md +40 -14
- package/docs/journeys/work-use.md +43 -15
- package/docs/manifest.json +27 -17
- package/package.json +3 -1
- package/src/app/api/chat/entities/search/route.ts +12 -3
- package/src/app/api/projects/[id]/route.ts +37 -0
- package/src/app/api/projects/__tests__/delete-project.test.ts +12 -0
- package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
- package/src/app/api/snapshots/[id]/route.ts +44 -0
- package/src/app/api/snapshots/route.ts +54 -0
- package/src/app/api/snapshots/settings/route.ts +67 -0
- package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
- package/src/app/api/tables/[id]/charts/route.ts +72 -0
- package/src/app/api/tables/[id]/columns/route.ts +70 -0
- package/src/app/api/tables/[id]/export/route.ts +94 -0
- package/src/app/api/tables/[id]/history/route.ts +15 -0
- package/src/app/api/tables/[id]/import/route.ts +111 -0
- package/src/app/api/tables/[id]/route.ts +86 -0
- package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
- package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
- package/src/app/api/tables/[id]/rows/route.ts +101 -0
- package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
- package/src/app/api/tables/[id]/triggers/route.ts +122 -0
- package/src/app/api/tables/route.ts +65 -0
- package/src/app/api/tables/templates/route.ts +92 -0
- package/src/app/globals.css +14 -0
- package/src/app/settings/page.tsx +2 -0
- package/src/app/tables/[id]/page.tsx +67 -0
- package/src/app/tables/page.tsx +21 -0
- package/src/app/tables/templates/page.tsx +19 -0
- package/src/components/book/book-reader.tsx +62 -9
- package/src/components/book/content-blocks.tsx +6 -1
- package/src/components/chat/chat-table-result.tsx +139 -0
- package/src/components/documents/document-browser.tsx +1 -1
- package/src/components/projects/project-form-sheet.tsx +3 -27
- package/src/components/schedules/schedule-form.tsx +5 -27
- package/src/components/settings/data-management-section.tsx +17 -12
- package/src/components/settings/database-snapshots-section.tsx +469 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/document-picker-sheet.tsx +214 -11
- package/src/components/tables/table-browser.tsx +234 -0
- package/src/components/tables/table-cell-editor.tsx +226 -0
- package/src/components/tables/table-chart-builder.tsx +288 -0
- package/src/components/tables/table-chart-view.tsx +146 -0
- package/src/components/tables/table-column-header.tsx +103 -0
- package/src/components/tables/table-column-sheet.tsx +331 -0
- package/src/components/tables/table-create-sheet.tsx +240 -0
- package/src/components/tables/table-detail-sheet.tsx +144 -0
- package/src/components/tables/table-detail-tabs.tsx +278 -0
- package/src/components/tables/table-grid.tsx +61 -0
- package/src/components/tables/table-history-tab.tsx +148 -0
- package/src/components/tables/table-import-wizard.tsx +542 -0
- package/src/components/tables/table-list-table.tsx +95 -0
- package/src/components/tables/table-relation-combobox.tsx +217 -0
- package/src/components/tables/table-row-sheet.tsx +271 -0
- package/src/components/tables/table-spreadsheet.tsx +394 -0
- package/src/components/tables/table-template-gallery.tsx +162 -0
- package/src/components/tables/table-template-preview.tsx +219 -0
- package/src/components/tables/table-toolbar.tsx +79 -0
- package/src/components/tables/table-triggers-tab.tsx +446 -0
- package/src/components/tables/types.ts +6 -0
- package/src/components/tables/use-spreadsheet-keys.ts +171 -0
- package/src/components/tables/utils.ts +29 -0
- package/src/components/tasks/task-create-panel.tsx +5 -31
- package/src/components/tasks/task-edit-dialog.tsx +5 -27
- package/src/components/workflows/workflow-form-view.tsx +11 -35
- package/src/components/workflows/workflow-status-view.tsx +1 -1
- package/src/instrumentation.ts +3 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
- package/src/lib/agents/claude-agent.ts +3 -1
- package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +23 -0
- package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +10 -0
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +1 -1
- package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
- package/src/lib/agents/runtime/openai-direct.ts +29 -0
- package/src/lib/book/chapter-generator.ts +81 -5
- package/src/lib/book/chapter-mapping.ts +58 -24
- package/src/lib/book/content.ts +83 -47
- package/src/lib/book/markdown-parser.ts +1 -1
- package/src/lib/book/reading-paths.ts +8 -8
- package/src/lib/book/types.ts +1 -1
- package/src/lib/book/update-detector.ts +4 -1
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/tool-catalog.ts +34 -0
- package/src/lib/chat/tools/table-tools.ts +955 -0
- package/src/lib/chat/tools/workflow-tools.ts +9 -1
- package/src/lib/constants/table-status.ts +68 -0
- package/src/lib/data/__tests__/clear.test.ts +1 -1
- package/src/lib/data/clear.ts +45 -0
- package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
- package/src/lib/data/seed-data/conversations.ts +350 -42
- package/src/lib/data/seed-data/documents.ts +564 -591
- package/src/lib/data/seed-data/learned-context.ts +101 -22
- package/src/lib/data/seed-data/notifications.ts +344 -70
- package/src/lib/data/seed-data/profile-test-results.ts +92 -11
- package/src/lib/data/seed-data/profiles.ts +144 -46
- package/src/lib/data/seed-data/projects.ts +50 -18
- package/src/lib/data/seed-data/repo-imports.ts +28 -13
- package/src/lib/data/seed-data/schedules.ts +208 -41
- package/src/lib/data/seed-data/table-templates.ts +234 -0
- package/src/lib/data/seed-data/tasks.ts +614 -116
- package/src/lib/data/seed-data/usage-ledger.ts +182 -103
- package/src/lib/data/seed-data/user-tables.ts +203 -0
- package/src/lib/data/seed-data/views.ts +52 -7
- package/src/lib/data/seed-data/workflows.ts +231 -84
- package/src/lib/data/seed.ts +55 -14
- package/src/lib/data/tables.ts +417 -0
- package/src/lib/db/bootstrap.ts +227 -0
- package/src/lib/db/index.ts +9 -0
- package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
- package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
- package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
- package/src/lib/db/schema.ts +368 -0
- package/src/lib/snapshots/auto-backup.ts +132 -0
- package/src/lib/snapshots/retention.ts +64 -0
- package/src/lib/snapshots/snapshot-manager.ts +429 -0
- package/src/lib/tables/computed.ts +61 -0
- package/src/lib/tables/context-builder.ts +139 -0
- package/src/lib/tables/formula-engine.ts +415 -0
- package/src/lib/tables/history.ts +115 -0
- package/src/lib/tables/import.ts +343 -0
- package/src/lib/tables/query-builder.ts +152 -0
- package/src/lib/tables/trigger-evaluator.ts +146 -0
- package/src/lib/tables/types.ts +141 -0
- package/src/lib/tables/validation.ts +119 -0
- package/src/lib/utils/stagent-paths.ts +20 -0
- package/src/lib/workflows/types.ts +1 -1
- package/tsconfig.json +3 -1
- /package/docs/features/{playbook.md → user-guide.md} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getTable, addColumn, getColumns } from "@/lib/data/tables";
|
|
3
|
+
import {
|
|
4
|
+
extractStructuredData,
|
|
5
|
+
inferColumnTypes,
|
|
6
|
+
importRows,
|
|
7
|
+
createImportRecord,
|
|
8
|
+
} from "@/lib/tables/import";
|
|
9
|
+
|
|
10
|
+
export async function POST(
|
|
11
|
+
req: NextRequest,
|
|
12
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
13
|
+
) {
|
|
14
|
+
const { id } = await params;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const table = await getTable(id);
|
|
18
|
+
if (!table) {
|
|
19
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = await req.json();
|
|
23
|
+
const { documentId, columnMapping, preview } = body as {
|
|
24
|
+
documentId?: string;
|
|
25
|
+
columnMapping?: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
displayName: string;
|
|
28
|
+
dataType: string;
|
|
29
|
+
skip?: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
preview?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (!documentId) {
|
|
35
|
+
return NextResponse.json(
|
|
36
|
+
{ error: "documentId is required" },
|
|
37
|
+
{ status: 400 }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Step 1: Extract structured data from the document
|
|
42
|
+
const { headers, rows } = await extractStructuredData(documentId);
|
|
43
|
+
|
|
44
|
+
// Step 2: Infer column types from a sample (first 100 rows)
|
|
45
|
+
const sampleRows = rows.slice(0, 100);
|
|
46
|
+
const inferredColumns = inferColumnTypes(headers, sampleRows);
|
|
47
|
+
|
|
48
|
+
// If preview mode, return data without importing
|
|
49
|
+
if (preview) {
|
|
50
|
+
return NextResponse.json({
|
|
51
|
+
headers,
|
|
52
|
+
sampleRows: rows.slice(0, 10),
|
|
53
|
+
totalRows: rows.length,
|
|
54
|
+
inferredColumns,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Step 3: Use user-provided column mapping or fall back to inferred
|
|
59
|
+
const finalColumns = columnMapping
|
|
60
|
+
? inferredColumns.map((col) => {
|
|
61
|
+
const mapped = columnMapping.find(
|
|
62
|
+
(m) => m.name === col.name || m.displayName === col.displayName
|
|
63
|
+
);
|
|
64
|
+
if (mapped?.skip) return null;
|
|
65
|
+
if (mapped) {
|
|
66
|
+
return {
|
|
67
|
+
...col,
|
|
68
|
+
name: mapped.name,
|
|
69
|
+
displayName: mapped.displayName,
|
|
70
|
+
dataType: mapped.dataType as typeof col.dataType,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return col;
|
|
74
|
+
}).filter(Boolean) as typeof inferredColumns
|
|
75
|
+
: inferredColumns;
|
|
76
|
+
|
|
77
|
+
// Step 4: Ensure columns exist on the table
|
|
78
|
+
const existingColumns = await getColumns(id);
|
|
79
|
+
const existingNames = new Set(existingColumns.map((c) => c.name));
|
|
80
|
+
|
|
81
|
+
for (const col of finalColumns) {
|
|
82
|
+
if (!existingNames.has(col.name)) {
|
|
83
|
+
await addColumn(id, {
|
|
84
|
+
name: col.name,
|
|
85
|
+
displayName: col.displayName,
|
|
86
|
+
dataType: col.dataType,
|
|
87
|
+
config: col.config,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Step 5: Import all rows
|
|
93
|
+
const result = await importRows(id, rows, finalColumns);
|
|
94
|
+
|
|
95
|
+
// Step 6: Create import audit record
|
|
96
|
+
await createImportRecord(id, documentId, result);
|
|
97
|
+
|
|
98
|
+
return NextResponse.json({
|
|
99
|
+
importId: result.importId,
|
|
100
|
+
rowsImported: result.rowsImported,
|
|
101
|
+
rowsSkipped: result.rowsSkipped,
|
|
102
|
+
errors: result.errors.slice(0, 20), // Limit error detail in response
|
|
103
|
+
columns: finalColumns,
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error("[tables/import] POST error:", err);
|
|
107
|
+
const message =
|
|
108
|
+
err instanceof Error ? err.message : "Failed to import data";
|
|
109
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
getTable,
|
|
4
|
+
updateTable,
|
|
5
|
+
deleteTable,
|
|
6
|
+
getColumns,
|
|
7
|
+
} from "@/lib/data/tables";
|
|
8
|
+
import { updateTableSchema } from "@/lib/tables/validation";
|
|
9
|
+
|
|
10
|
+
export async function GET(
|
|
11
|
+
_req: NextRequest,
|
|
12
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
13
|
+
) {
|
|
14
|
+
const { id } = await params;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const table = await getTable(id);
|
|
18
|
+
if (!table) {
|
|
19
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const columns = await getColumns(id);
|
|
23
|
+
return NextResponse.json({ ...table, columns });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("[tables] GET by id error:", err);
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: "Failed to get table" },
|
|
28
|
+
{ status: 500 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function PATCH(
|
|
34
|
+
req: NextRequest,
|
|
35
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
36
|
+
) {
|
|
37
|
+
const { id } = await params;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const body = await req.json();
|
|
41
|
+
const parsed = updateTableSchema.safeParse(body);
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{ error: parsed.error.flatten() },
|
|
45
|
+
{ status: 400 }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const existing = await getTable(id);
|
|
50
|
+
if (!existing) {
|
|
51
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const updated = await updateTable(id, parsed.data);
|
|
55
|
+
return NextResponse.json(updated);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error("[tables] PATCH error:", err);
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: "Failed to update table" },
|
|
60
|
+
{ status: 500 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function DELETE(
|
|
66
|
+
_req: NextRequest,
|
|
67
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
68
|
+
) {
|
|
69
|
+
const { id } = await params;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const existing = await getTable(id);
|
|
73
|
+
if (!existing) {
|
|
74
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await deleteTable(id);
|
|
78
|
+
return new NextResponse(null, { status: 204 });
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error("[tables] DELETE error:", err);
|
|
81
|
+
return NextResponse.json(
|
|
82
|
+
{ error: "Failed to delete table" },
|
|
83
|
+
{ status: 500 }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getRowHistory, rollbackRow } from "@/lib/tables/history";
|
|
3
|
+
|
|
4
|
+
interface RouteContext {
|
|
5
|
+
params: Promise<{ id: string; rowId: string }>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function GET(req: NextRequest, { params }: RouteContext) {
|
|
9
|
+
const { rowId } = await params;
|
|
10
|
+
const url = new URL(req.url);
|
|
11
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "50", 10), 200);
|
|
12
|
+
|
|
13
|
+
const history = getRowHistory(rowId, limit);
|
|
14
|
+
return NextResponse.json(history);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function POST(req: NextRequest, { params }: RouteContext) {
|
|
18
|
+
await params; // validate route
|
|
19
|
+
const body = await req.json();
|
|
20
|
+
const { historyEntryId } = body as { historyEntryId?: string };
|
|
21
|
+
|
|
22
|
+
if (!historyEntryId) {
|
|
23
|
+
return NextResponse.json({ error: "historyEntryId is required" }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const success = rollbackRow(historyEntryId);
|
|
27
|
+
if (!success) {
|
|
28
|
+
return NextResponse.json({ error: "History entry not found" }, { status: 404 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({ ok: true });
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { updateRow, deleteRows } from "@/lib/data/tables";
|
|
3
|
+
import { updateRowSchema } from "@/lib/tables/validation";
|
|
4
|
+
|
|
5
|
+
export async function PATCH(
|
|
6
|
+
req: NextRequest,
|
|
7
|
+
{ params }: { params: Promise<{ id: string; rowId: string }> }
|
|
8
|
+
) {
|
|
9
|
+
const { rowId } = await params;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const body = await req.json();
|
|
13
|
+
const parsed = updateRowSchema.safeParse(body);
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: parsed.error.flatten() },
|
|
17
|
+
{ status: 400 }
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const row = await updateRow(rowId, parsed.data);
|
|
22
|
+
if (!row) {
|
|
23
|
+
return NextResponse.json({ error: "Row not found" }, { status: 404 });
|
|
24
|
+
}
|
|
25
|
+
return NextResponse.json(row);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error("[tables] PATCH row error:", err);
|
|
28
|
+
return NextResponse.json(
|
|
29
|
+
{ error: "Failed to update row" },
|
|
30
|
+
{ status: 500 }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function DELETE(
|
|
36
|
+
_req: NextRequest,
|
|
37
|
+
{ params }: { params: Promise<{ id: string; rowId: string }> }
|
|
38
|
+
) {
|
|
39
|
+
const { rowId } = await params;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await deleteRows([rowId]);
|
|
43
|
+
return new NextResponse(null, { status: 204 });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error("[tables] DELETE row error:", err);
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: "Failed to delete row" },
|
|
48
|
+
{ status: 500 }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getTable, listRows, addRows } from "@/lib/data/tables";
|
|
3
|
+
import { rowQuerySchema, addRowsSchema } from "@/lib/tables/validation";
|
|
4
|
+
|
|
5
|
+
export async function GET(
|
|
6
|
+
req: NextRequest,
|
|
7
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
8
|
+
) {
|
|
9
|
+
const { id } = await params;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const existing = await getTable(id);
|
|
13
|
+
if (!existing) {
|
|
14
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const url = new URL(req.url);
|
|
18
|
+
const limit = url.searchParams.get("limit")
|
|
19
|
+
? Number(url.searchParams.get("limit"))
|
|
20
|
+
: undefined;
|
|
21
|
+
const offset = url.searchParams.get("offset")
|
|
22
|
+
? Number(url.searchParams.get("offset"))
|
|
23
|
+
: undefined;
|
|
24
|
+
|
|
25
|
+
let filters: unknown;
|
|
26
|
+
let sorts: unknown;
|
|
27
|
+
|
|
28
|
+
const filtersParam = url.searchParams.get("filters");
|
|
29
|
+
if (filtersParam) {
|
|
30
|
+
try {
|
|
31
|
+
filters = JSON.parse(filtersParam);
|
|
32
|
+
} catch {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: "Invalid filters JSON" },
|
|
35
|
+
{ status: 400 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sortsParam = url.searchParams.get("sorts");
|
|
41
|
+
if (sortsParam) {
|
|
42
|
+
try {
|
|
43
|
+
sorts = JSON.parse(sortsParam);
|
|
44
|
+
} catch {
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{ error: "Invalid sorts JSON" },
|
|
47
|
+
{ status: 400 }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parsed = rowQuerySchema.safeParse({ limit, offset, filters, sorts });
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: parsed.error.flatten() },
|
|
56
|
+
{ status: 400 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rows = await listRows(id, parsed.data);
|
|
61
|
+
return NextResponse.json(rows);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error("[tables] GET rows error:", err);
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: "Failed to list rows" },
|
|
66
|
+
{ status: 500 }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function POST(
|
|
72
|
+
req: NextRequest,
|
|
73
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
74
|
+
) {
|
|
75
|
+
const { id } = await params;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const existing = await getTable(id);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const body = await req.json();
|
|
84
|
+
const parsed = addRowsSchema.safeParse(body);
|
|
85
|
+
if (!parsed.success) {
|
|
86
|
+
return NextResponse.json(
|
|
87
|
+
{ error: parsed.error.flatten() },
|
|
88
|
+
{ status: 400 }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const rows = await addRows(id, parsed.data.rows);
|
|
93
|
+
return NextResponse.json(rows, { status: 201 });
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error("[tables] POST rows error:", err);
|
|
96
|
+
return NextResponse.json(
|
|
97
|
+
{ error: "Failed to add rows" },
|
|
98
|
+
{ status: 500 }
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { userTableTriggers } from "@/lib/db/schema";
|
|
4
|
+
import { eq, and } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
interface RouteContext {
|
|
7
|
+
params: Promise<{ id: string; triggerId: string }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function PATCH(req: NextRequest, { params }: RouteContext) {
|
|
11
|
+
const { id, triggerId } = await params;
|
|
12
|
+
|
|
13
|
+
const trigger = db
|
|
14
|
+
.select()
|
|
15
|
+
.from(userTableTriggers)
|
|
16
|
+
.where(and(eq(userTableTriggers.id, triggerId), eq(userTableTriggers.tableId, id)))
|
|
17
|
+
.get();
|
|
18
|
+
|
|
19
|
+
if (!trigger) {
|
|
20
|
+
return NextResponse.json({ error: "Trigger not found" }, { status: 404 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const body = await req.json();
|
|
24
|
+
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
|
25
|
+
|
|
26
|
+
if (body.name !== undefined) updates.name = body.name;
|
|
27
|
+
if (body.triggerEvent !== undefined) updates.triggerEvent = body.triggerEvent;
|
|
28
|
+
if (body.status !== undefined) updates.status = body.status;
|
|
29
|
+
if (body.condition !== undefined) updates.condition = body.condition ? JSON.stringify(body.condition) : null;
|
|
30
|
+
if (body.actionType !== undefined) updates.actionType = body.actionType;
|
|
31
|
+
if (body.actionConfig !== undefined) updates.actionConfig = JSON.stringify(body.actionConfig);
|
|
32
|
+
|
|
33
|
+
db.update(userTableTriggers)
|
|
34
|
+
.set(updates)
|
|
35
|
+
.where(eq(userTableTriggers.id, triggerId))
|
|
36
|
+
.run();
|
|
37
|
+
|
|
38
|
+
const updated = db
|
|
39
|
+
.select()
|
|
40
|
+
.from(userTableTriggers)
|
|
41
|
+
.where(eq(userTableTriggers.id, triggerId))
|
|
42
|
+
.get();
|
|
43
|
+
|
|
44
|
+
return NextResponse.json(updated);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function DELETE(_req: NextRequest, { params }: RouteContext) {
|
|
48
|
+
const { id, triggerId } = await params;
|
|
49
|
+
|
|
50
|
+
const trigger = db
|
|
51
|
+
.select()
|
|
52
|
+
.from(userTableTriggers)
|
|
53
|
+
.where(and(eq(userTableTriggers.id, triggerId), eq(userTableTriggers.tableId, id)))
|
|
54
|
+
.get();
|
|
55
|
+
|
|
56
|
+
if (!trigger) {
|
|
57
|
+
return NextResponse.json({ error: "Trigger not found" }, { status: 404 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
db.delete(userTableTriggers)
|
|
61
|
+
.where(eq(userTableTriggers.id, triggerId))
|
|
62
|
+
.run();
|
|
63
|
+
|
|
64
|
+
return new NextResponse(null, { status: 204 });
|
|
65
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { db } from "@/lib/db";
|
|
4
|
+
import { userTableTriggers } from "@/lib/db/schema";
|
|
5
|
+
import { eq } from "drizzle-orm";
|
|
6
|
+
import { getTable } from "@/lib/data/tables";
|
|
7
|
+
|
|
8
|
+
export async function GET(
|
|
9
|
+
_req: NextRequest,
|
|
10
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
11
|
+
) {
|
|
12
|
+
const { id } = await params;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const table = await getTable(id);
|
|
16
|
+
if (!table) {
|
|
17
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const triggers = db
|
|
21
|
+
.select()
|
|
22
|
+
.from(userTableTriggers)
|
|
23
|
+
.where(eq(userTableTriggers.tableId, id))
|
|
24
|
+
.all();
|
|
25
|
+
|
|
26
|
+
return NextResponse.json(triggers);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("[tables/triggers] GET error:", err);
|
|
29
|
+
return NextResponse.json({ error: "Failed to list triggers" }, { status: 500 });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function POST(
|
|
34
|
+
req: NextRequest,
|
|
35
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
36
|
+
) {
|
|
37
|
+
const { id } = await params;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const table = await getTable(id);
|
|
41
|
+
if (!table) {
|
|
42
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const body = await req.json();
|
|
46
|
+
const { name, triggerEvent, condition, actionType, actionConfig } = body as {
|
|
47
|
+
name?: string;
|
|
48
|
+
triggerEvent?: string;
|
|
49
|
+
condition?: unknown;
|
|
50
|
+
actionType?: string;
|
|
51
|
+
actionConfig?: unknown;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (!name || !triggerEvent || !actionType || !actionConfig) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: "name, triggerEvent, actionType, and actionConfig are required" },
|
|
57
|
+
{ status: 400 }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const now = new Date();
|
|
62
|
+
const triggerId = randomUUID();
|
|
63
|
+
|
|
64
|
+
db.insert(userTableTriggers)
|
|
65
|
+
.values({
|
|
66
|
+
id: triggerId,
|
|
67
|
+
tableId: id,
|
|
68
|
+
name,
|
|
69
|
+
triggerEvent: triggerEvent as "row_added" | "row_updated" | "row_deleted",
|
|
70
|
+
condition: condition ? JSON.stringify(condition) : null,
|
|
71
|
+
actionType: actionType as "run_workflow" | "create_task",
|
|
72
|
+
actionConfig: JSON.stringify(actionConfig),
|
|
73
|
+
status: "active",
|
|
74
|
+
fireCount: 0,
|
|
75
|
+
createdAt: now,
|
|
76
|
+
updatedAt: now,
|
|
77
|
+
})
|
|
78
|
+
.run();
|
|
79
|
+
|
|
80
|
+
const trigger = db
|
|
81
|
+
.select()
|
|
82
|
+
.from(userTableTriggers)
|
|
83
|
+
.where(eq(userTableTriggers.id, triggerId))
|
|
84
|
+
.get();
|
|
85
|
+
|
|
86
|
+
return NextResponse.json(trigger, { status: 201 });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error("[tables/triggers] POST error:", err);
|
|
89
|
+
return NextResponse.json({ error: "Failed to create trigger" }, { status: 500 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function PATCH(
|
|
94
|
+
req: NextRequest,
|
|
95
|
+
{ params: _params }: { params: Promise<{ id: string }> }
|
|
96
|
+
) {
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const body = await req.json();
|
|
100
|
+
const { triggerId, status } = body as { triggerId?: string; status?: string };
|
|
101
|
+
|
|
102
|
+
if (!triggerId || !status) {
|
|
103
|
+
return NextResponse.json(
|
|
104
|
+
{ error: "triggerId and status are required" },
|
|
105
|
+
{ status: 400 }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
db.update(userTableTriggers)
|
|
110
|
+
.set({
|
|
111
|
+
status: status as "active" | "paused",
|
|
112
|
+
updatedAt: new Date(),
|
|
113
|
+
})
|
|
114
|
+
.where(eq(userTableTriggers.id, triggerId))
|
|
115
|
+
.run();
|
|
116
|
+
|
|
117
|
+
return NextResponse.json({ success: true });
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error("[tables/triggers] PATCH error:", err);
|
|
120
|
+
return NextResponse.json({ error: "Failed to update trigger" }, { status: 500 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
listTables,
|
|
4
|
+
createTable,
|
|
5
|
+
cloneFromTemplate,
|
|
6
|
+
} from "@/lib/data/tables";
|
|
7
|
+
import {
|
|
8
|
+
createTableSchema,
|
|
9
|
+
cloneFromTemplateSchema,
|
|
10
|
+
} from "@/lib/tables/validation";
|
|
11
|
+
|
|
12
|
+
export async function GET(req: NextRequest) {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(req.url);
|
|
15
|
+
const projectId = url.searchParams.get("projectId") ?? undefined;
|
|
16
|
+
const source = url.searchParams.get("source") ?? undefined;
|
|
17
|
+
const search = url.searchParams.get("search") ?? undefined;
|
|
18
|
+
|
|
19
|
+
const tables = await listTables({ projectId, source, search });
|
|
20
|
+
return NextResponse.json(tables);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error("[tables] GET error:", err);
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{ error: "Failed to list tables" },
|
|
25
|
+
{ status: 500 }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function POST(req: NextRequest) {
|
|
31
|
+
try {
|
|
32
|
+
const body = await req.json();
|
|
33
|
+
|
|
34
|
+
// Template clone path
|
|
35
|
+
if (body.templateId) {
|
|
36
|
+
const parsed = cloneFromTemplateSchema.safeParse(body);
|
|
37
|
+
if (!parsed.success) {
|
|
38
|
+
return NextResponse.json(
|
|
39
|
+
{ error: parsed.error.flatten() },
|
|
40
|
+
{ status: 400 }
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const table = await cloneFromTemplate(parsed.data);
|
|
44
|
+
return NextResponse.json(table, { status: 201 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Normal create path
|
|
48
|
+
const parsed = createTableSchema.safeParse(body);
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
{ error: parsed.error.flatten() },
|
|
52
|
+
{ status: 400 }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const table = await createTable(parsed.data);
|
|
57
|
+
return NextResponse.json(table, { status: 201 });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error("[tables] POST error:", err);
|
|
60
|
+
return NextResponse.json(
|
|
61
|
+
{ error: "Failed to create table" },
|
|
62
|
+
{ status: 500 }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|