diff --git a/src/app/api/internal/seed/route.ts b/src/app/api/internal/seed/route.ts new file mode 100644 index 0000000..2c6e154 --- /dev/null +++ b/src/app/api/internal/seed/route.ts @@ -0,0 +1,242 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +/** + * Internal seed endpoint — populates the demo workspace with sample + * notebooks, notes, and tags. Only reachable from Docker network. + * + * POST /api/internal/seed { space: "demo" } + */ +export async function POST(request: Request) { + const body = await request.json(); + const spaceSlug: string = body.space?.trim(); + if (!spaceSlug) { + return NextResponse.json({ error: "Missing space" }, { status: 400 }); + } + + // Check if already seeded + const existingNotes = await prisma.note.count({ + where: { notebook: { workspaceSlug: spaceSlug } }, + }); + if (existingNotes > 0) { + return NextResponse.json({ status: "already_seeded", notes: existingNotes }); + } + + // Create demo user + const alice = await prisma.user.upsert({ + where: { did: "did:demo:alice" }, + update: {}, + create: { did: "did:demo:alice", username: "Alice" }, + }); + + // ─── Notebook 1: Research ──────────────────────────────────── + const research = await prisma.notebook.create({ + data: { + title: "Open Source Governance Research", + slug: `${spaceSlug}-governance-research`, + description: "Patterns and case studies in decentralized decision-making", + coverColor: "#3b82f6", + isPublic: true, + workspaceSlug: spaceSlug, + collaborators: { create: { userId: alice.id, role: "OWNER" } }, + }, + }); + + // Create tags + const tagGovernance = await prisma.tag.create({ + data: { name: "governance", color: "#8b5cf6", spaceId: spaceSlug }, + }); + const tagWeb3 = await prisma.tag.create({ + data: { name: "web3", color: "#06b6d4", spaceId: spaceSlug }, + }); + const tagIdeas = await prisma.tag.create({ + data: { name: "ideas", color: "#f59e0b", spaceId: spaceSlug }, + }); + const tagMeeting = await prisma.tag.create({ + data: { name: "meeting-notes", color: "#10b981", spaceId: spaceSlug }, + }); + + // Notes in research notebook + const note1 = await prisma.note.create({ + data: { + notebookId: research.id, + authorId: alice.id, + title: "Quadratic Voting: Fair Group Decisions", + content: + "

Quadratic voting lets participants express intensity of preference, not just direction. Cost of votes scales quadratically — 1 vote costs 1 credit, 2 votes cost 4, 3 cost 9, etc.

This prevents whale dominance while letting those who care most have a stronger voice. Used by Gitcoin, RadicalxChange, and Colorado state legislature experiments.

", + type: "NOTE", + cardType: "reference", + visibility: "public", + isPinned: true, + position: 1, + }, + }); + await prisma.noteTag.createMany({ + data: [ + { noteId: note1.id, tagId: tagGovernance.id }, + { noteId: note1.id, tagId: tagWeb3.id }, + ], + }); + + const note2 = await prisma.note.create({ + data: { + notebookId: research.id, + authorId: alice.id, + title: "Gitcoin Grants: Quadratic Funding in Practice", + content: + "

Gitcoin has distributed $50M+ using quadratic funding. The number of contributors matters more than amount — many small donations get amplified by the matching pool.

Key insight: QF aligns funding with community preference, not just whale wallets.

", + type: "BOOKMARK", + url: "https://gitcoin.co", + cardType: "link", + visibility: "public", + position: 2, + }, + }); + await prisma.noteTag.createMany({ + data: [ + { noteId: note2.id, tagId: tagGovernance.id }, + { noteId: note2.id, tagId: tagWeb3.id }, + ], + }); + + await prisma.note.create({ + data: { + notebookId: research.id, + authorId: alice.id, + title: "DAO Treasury Management Patterns", + content: + "

Multi-sig wallets (like Safe) reduce speed but increase trust. Common patterns:

The hardest part isn't the tech — it's agreeing on priorities.

", + type: "NOTE", + cardType: "idea", + visibility: "space", + position: 3, + }, + }); + + await prisma.note.create({ + data: { + notebookId: research.id, + authorId: alice.id, + title: "Consent-Based Decision Making", + content: + "

Unlike consensus (everyone agrees), consent means no one has a principled objection. Much faster for groups.

Process: Propose → Clarify → React → Amend → Consent check. If no objections, proceed. Objections are gifts — they reveal risks.

", + type: "NOTE", + cardType: "reference", + visibility: "public", + position: 4, + }, + }); + + // ─── Notebook 2: Meeting Notes ──────────────────────────────── + const meetings = await prisma.notebook.create({ + data: { + title: "Community Meetings", + slug: `${spaceSlug}-meetings`, + description: "Notes and action items from our regular check-ins", + coverColor: "#f59e0b", + isPublic: true, + workspaceSlug: spaceSlug, + collaborators: { create: { userId: alice.id, role: "OWNER" } }, + }, + }); + + const meeting1 = await prisma.note.create({ + data: { + notebookId: meetings.id, + authorId: alice.id, + title: "Weekly Standup — Feb 24", + content: + "

Attendees

Alice, Bob, Charlie

Updates

Blockers

None this week

", + type: "NOTE", + cardType: "note", + visibility: "space", + isPinned: true, + position: 1, + }, + }); + await prisma.noteTag.create({ + data: { noteId: meeting1.id, tagId: tagMeeting.id }, + }); + + // Action items as child notes + await prisma.note.create({ + data: { + notebookId: meetings.id, + authorId: alice.id, + parentId: meeting1.id, + title: "Action: Set up quadratic voting for treasury proposals", + content: "

Alice to configure rvote with 7-day voting period and 50 starting credits per member.

", + type: "NOTE", + cardType: "task", + visibility: "space", + position: 1.1, + }, + }); + + await prisma.note.create({ + data: { + notebookId: meetings.id, + authorId: alice.id, + parentId: meeting1.id, + title: "Action: Share garden cart link with broader community", + content: "

Bob to post the rcart link in rsocials to get more contributors.

", + type: "NOTE", + cardType: "task", + visibility: "space", + position: 1.2, + }, + }); + + // ─── Notebook 3: Ideas ──────────────────────────────────────── + const ideas = await prisma.notebook.create({ + data: { + title: "Project Ideas", + slug: `${spaceSlug}-ideas`, + description: "Brainstorms, what-ifs, and possibilities", + coverColor: "#ec4899", + isPublic: true, + workspaceSlug: spaceSlug, + collaborators: { create: { userId: alice.id, role: "OWNER" } }, + }, + }); + + const idea1 = await prisma.note.create({ + data: { + notebookId: ideas.id, + authorId: alice.id, + title: "Community Currency / Time Banking", + content: + "

What if we tracked contributions in a local currency? Members earn credits for volunteering, teaching, or helping — then spend them on services from other members.

Tools to explore: Circles UBI, Grassroots Economics, or a simple ledger in rcart.

", + type: "NOTE", + cardType: "idea", + visibility: "public", + isPinned: true, + position: 1, + }, + }); + await prisma.noteTag.create({ + data: { noteId: idea1.id, tagId: tagIdeas.id }, + }); + + await prisma.note.create({ + data: { + notebookId: ideas.id, + authorId: alice.id, + title: "Skill-Share Wednesdays", + content: + "

Rotating weekly sessions where someone teaches something they know. Could be cooking, coding, gardening, music — anything. Low pressure, high connection.

", + type: "NOTE", + cardType: "idea", + visibility: "public", + position: 2, + }, + }); + + return NextResponse.json({ + status: "seeded", + space: spaceSlug, + notebooks: 3, + notes: 9, + tags: 4, + }, { status: 201 }); +}