stagent 0.6.2 → 0.7.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 +272 -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 -2
- package/src/app/api/chat/entities/search/route.ts +12 -3
- package/src/app/api/documents/[id]/route.ts +5 -1
- package/src/app/api/documents/[id]/versions/route.ts +53 -0
- package/src/app/api/documents/route.ts +5 -1
- package/src/app/api/projects/[id]/documents/route.ts +124 -0
- package/src/app/api/projects/[id]/route.ts +72 -3
- package/src/app/api/projects/__tests__/delete-project.test.ts +13 -0
- package/src/app/api/schedules/route.ts +19 -1
- 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/api/tasks/[id]/route.ts +37 -2
- package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
- package/src/app/api/tasks/route.ts +8 -9
- package/src/app/api/workflows/[id]/documents/route.ts +209 -0
- package/src/app/api/workflows/[id]/execute/route.ts +6 -2
- package/src/app/api/workflows/[id]/route.ts +16 -3
- package/src/app/api/workflows/[id]/status/route.ts +18 -2
- package/src/app/api/workflows/route.ts +13 -2
- package/src/app/documents/page.tsx +5 -1
- package/src/app/layout.tsx +0 -1
- package/src/app/manifest.ts +3 -3
- package/src/app/projects/[id]/page.tsx +62 -2
- 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/chat/chat-table-result.tsx +139 -0
- package/src/components/documents/document-browser.tsx +1 -1
- package/src/components/documents/document-chip-bar.tsx +17 -1
- package/src/components/documents/document-detail-view.tsx +51 -0
- package/src/components/documents/document-grid.tsx +5 -0
- package/src/components/documents/document-table.tsx +4 -0
- package/src/components/documents/types.ts +3 -0
- package/src/components/projects/project-form-sheet.tsx +109 -2
- package/src/components/schedules/schedule-form.tsx +91 -1
- 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 +486 -0
- 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-spreadsheet.tsx +499 -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-card.tsx +8 -1
- package/src/components/tasks/task-create-panel.tsx +111 -14
- package/src/components/tasks/task-detail-view.tsx +47 -0
- package/src/components/tasks/task-edit-dialog.tsx +103 -2
- package/src/components/workflows/workflow-form-view.tsx +207 -7
- package/src/components/workflows/workflow-kanban-card.tsx +8 -1
- package/src/components/workflows/workflow-list.tsx +90 -45
- package/src/components/workflows/workflow-status-view.tsx +168 -23
- package/src/instrumentation.ts +3 -0
- package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
- 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/registry.ts +6 -3
- package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
- package/src/lib/agents/runtime/openai-direct.ts +29 -0
- package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
- package/src/lib/book/chapter-generator.ts +4 -19
- package/src/lib/book/chapter-mapping.ts +17 -0
- package/src/lib/book/content.ts +5 -16
- package/src/lib/book/update-detector.ts +3 -16
- package/src/lib/chat/engine.ts +1 -0
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/system-prompt.ts +9 -1
- package/src/lib/chat/tool-catalog.ts +35 -0
- package/src/lib/chat/tools/settings-tools.ts +109 -0
- package/src/lib/chat/tools/table-tools.ts +955 -0
- package/src/lib/chat/tools/workflow-tools.ts +145 -2
- 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 +57 -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 +275 -0
- package/src/lib/db/index.ts +9 -0
- package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
- package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
- package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -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 +445 -0
- package/src/lib/docs/reader.ts +2 -3
- package/src/lib/documents/context-builder.ts +75 -2
- package/src/lib/documents/document-resolver.ts +119 -0
- package/src/lib/documents/processors/spreadsheet.ts +2 -1
- package/src/lib/schedules/scheduler.ts +31 -1
- 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/app-root.ts +20 -0
- package/src/lib/utils/stagent-paths.ts +20 -0
- package/src/lib/validators/__tests__/task.test.ts +43 -10
- package/src/lib/validators/task.ts +7 -1
- package/src/lib/workflows/blueprints/registry.ts +3 -3
- package/src/lib/workflows/engine.ts +24 -8
- package/src/lib/workflows/types.ts +14 -0
- package/tsconfig.json +3 -1
- package/public/icon.svg +0 -13
- package/src/components/tasks/file-upload.tsx +0 -120
- /package/docs/features/{playbook.md → user-guide.md} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { db } from "@/lib/db";
|
|
4
|
+
import { userTableTemplates } from "@/lib/db/schema";
|
|
5
|
+
import { listTemplates, getTable, listRows } from "@/lib/data/tables";
|
|
6
|
+
import { listTemplatesSchema } from "@/lib/tables/validation";
|
|
7
|
+
|
|
8
|
+
export async function GET(req: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(req.url);
|
|
11
|
+
const category = url.searchParams.get("category") ?? undefined;
|
|
12
|
+
const scope = url.searchParams.get("scope") ?? undefined;
|
|
13
|
+
|
|
14
|
+
const parsed = listTemplatesSchema.safeParse({ category, scope });
|
|
15
|
+
if (!parsed.success) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ error: parsed.error.flatten() },
|
|
18
|
+
{ status: 400 }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const templates = await listTemplates(parsed.data);
|
|
23
|
+
return NextResponse.json(templates);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("[tables] GET templates error:", err);
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: "Failed to list templates" },
|
|
28
|
+
{ status: 500 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* POST /api/tables/templates — Save a table as a user-scoped template.
|
|
35
|
+
*/
|
|
36
|
+
export async function POST(req: NextRequest) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await req.json();
|
|
39
|
+
const { tableId, name, description, category, includeSampleData } = body as {
|
|
40
|
+
tableId?: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
category?: string;
|
|
44
|
+
includeSampleData?: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (!tableId || !name) {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: "tableId and name are required" },
|
|
50
|
+
{ status: 400 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const table = await getTable(tableId);
|
|
55
|
+
if (!table) {
|
|
56
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Optionally snapshot first 5 rows as sample data
|
|
60
|
+
let sampleData: string | null = null;
|
|
61
|
+
if (includeSampleData) {
|
|
62
|
+
const rows = await listRows(tableId, { limit: 5 });
|
|
63
|
+
sampleData = JSON.stringify(rows.map((r) => JSON.parse(r.data)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const now = new Date();
|
|
67
|
+
const templateId = randomUUID();
|
|
68
|
+
|
|
69
|
+
db.insert(userTableTemplates)
|
|
70
|
+
.values({
|
|
71
|
+
id: templateId,
|
|
72
|
+
name,
|
|
73
|
+
description: description ?? table.description,
|
|
74
|
+
category: (category as "business" | "personal" | "pm" | "finance" | "content") ?? "personal",
|
|
75
|
+
columnSchema: table.columnSchema,
|
|
76
|
+
sampleData,
|
|
77
|
+
scope: "user",
|
|
78
|
+
icon: null,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
})
|
|
82
|
+
.run();
|
|
83
|
+
|
|
84
|
+
return NextResponse.json({ id: templateId, name }, { status: 201 });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error("[tables] POST templates error:", err);
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: "Failed to save template" },
|
|
89
|
+
{ status: 500 }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { db } from "@/lib/db";
|
|
3
|
-
import { tasks, projects, workflows, schedules, usageLedger } from "@/lib/db/schema";
|
|
3
|
+
import { tasks, projects, workflows, schedules, usageLedger, documents } from "@/lib/db/schema";
|
|
4
4
|
import { eq, sum, min, max } from "drizzle-orm";
|
|
5
5
|
import { updateTaskSchema } from "@/lib/validators/task";
|
|
6
6
|
import { isValidTransition, type TaskStatus } from "@/lib/constants/task-status";
|
|
@@ -106,11 +106,46 @@ export async function PATCH(
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// Extract documentIds before spreading into task update (not a task column)
|
|
110
|
+
const { documentIds, ...taskFields } = parsed.data;
|
|
111
|
+
const now = new Date();
|
|
112
|
+
|
|
109
113
|
await db
|
|
110
114
|
.update(tasks)
|
|
111
|
-
.set({ ...
|
|
115
|
+
.set({ ...taskFields, updatedAt: now })
|
|
112
116
|
.where(eq(tasks.id, id));
|
|
113
117
|
|
|
118
|
+
// Handle document linking/unlinking
|
|
119
|
+
if (documentIds !== undefined) {
|
|
120
|
+
try {
|
|
121
|
+
// Unlink documents previously linked to this task that are no longer selected
|
|
122
|
+
const currentDocs = await db
|
|
123
|
+
.select({ id: documents.id })
|
|
124
|
+
.from(documents)
|
|
125
|
+
.where(eq(documents.taskId, id));
|
|
126
|
+
const newDocSet = new Set(documentIds);
|
|
127
|
+
for (const doc of currentDocs) {
|
|
128
|
+
if (!newDocSet.has(doc.id)) {
|
|
129
|
+
await db.update(documents)
|
|
130
|
+
.set({ taskId: null, updatedAt: now })
|
|
131
|
+
.where(eq(documents.id, doc.id));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Link newly selected documents
|
|
135
|
+
for (const docId of documentIds) {
|
|
136
|
+
await db.update(documents)
|
|
137
|
+
.set({
|
|
138
|
+
taskId: id,
|
|
139
|
+
projectId: existing.projectId,
|
|
140
|
+
updatedAt: now,
|
|
141
|
+
})
|
|
142
|
+
.where(eq(documents.id, docId));
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error("[tasks] Document association failed:", err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
114
149
|
const [updated] = await db.select().from(tasks).where(eq(tasks.id, id));
|
|
115
150
|
return NextResponse.json(updated);
|
|
116
151
|
}
|