import { preparePublicGitSource, type ProjectImportOutcome, type ProjectView, type PublicGitImportResult, type PreparedPublicGitSource, } from "@code/primitives/project"; import { v } from "convex/values"; import { Effect } from "effect"; import type { Id } from "./_generated/dataModel"; import { action, internalMutation, query } from "./_generated/server"; import { createInitialArtifacts } from "./artifactModel"; import { requireCurrentOrganization } from "./authz"; import { makeProjectMutationStore, makeProjectQueryStore, persistPublicGitImportTransaction, } from "./projectStore"; // --------------------------------------------------------------------------- // Internal: persist a public Git import in one transaction // --------------------------------------------------------------------------- export const persistPublicGitImport = internalMutation({ args: { userId: v.string(), source: v.object({ host: v.string(), projectName: v.string(), repositoryPath: v.string(), normalizedUrl: v.string(), url: v.string(), }), remote: v.object({ defaultBranch: v.optional(v.string()), documents: v.array( v.object({ kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), path: v.string(), content: v.string(), }) ), warnings: v.array( v.object({ path: v.string(), message: v.string(), }) ), }), }, handler: async (ctx, args): Promise => { const result = await persistPublicGitImportTransaction( ctx, args.userId, args.source as PreparedPublicGitSource, args.remote as PublicGitImportResult ); // Seed operational artifacts on first creation (detected by checking // existing artifacts) const existingArtifacts = await ctx.db .query("projectArtifacts") .withIndex("by_project", (q) => q.eq("projectId", result.id as unknown as Id<"projects">) ) .first(); if (!existingArtifacts) { const now = Date.now(); const artifacts = createInitialArtifacts(); await Promise.all( artifacts.map(({ content, path }) => ctx.db.insert("projectArtifacts", { content, createdAt: now, path, projectId: result.id as unknown as Id<"projects">, revision: 1, updatedAt: now, }) ) ); await ctx.db.insert("projectEvents", { createdAt: now, data: { host: result.source.host, url: result.source.url }, kind: "project.connected", projectId: result.id as unknown as Id<"projects">, }); } return result; }, }); // --------------------------------------------------------------------------- // Internal: put a context document // --------------------------------------------------------------------------- export const putContextDocument = internalMutation({ args: { userId: v.string(), projectId: v.id("projects"), write: v.union( v.object({ _tag: v.literal("Repository"), content: v.string(), kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), sourceUrl: v.string(), }), v.object({ _tag: v.literal("PublicTextUrl"), content: v.string(), kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), sourceUrl: v.string(), }), v.object({ _tag: v.literal("UserText"), content: v.string(), kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), origin: v.union(v.literal("paste"), v.literal("upload")), }) ), }, handler: async (ctx, args) => { const store = makeProjectMutationStore(ctx); return store.putContext(args.userId, args.projectId, args.write as never); }, }); // --------------------------------------------------------------------------- // Public query: list projects // --------------------------------------------------------------------------- export const list = query({ args: {}, handler: async (ctx): Promise => { const { userId } = await requireCurrentOrganization(ctx); const store = makeProjectQueryStore(ctx); return store.listProjects(userId); }, }); // --------------------------------------------------------------------------- // Public query: get a project // --------------------------------------------------------------------------- export const get = query({ args: { projectId: v.id("projects") }, handler: async (ctx, args): Promise => { const { userId } = await requireCurrentOrganization(ctx); const store = makeProjectQueryStore(ctx); return store.getProject(userId, args.projectId); }, }); // --------------------------------------------------------------------------- // Public action: import a public Git repository // (daemon wiring added in the Daemon phase) // --------------------------------------------------------------------------- export const importPublicGit = action({ args: { repositoryUrl: v.string() }, handler: async (_ctx, args): Promise => { // Normalize through the domain layer to validate the URL before the // daemon adapter is wired. await Effect.runPromise(preparePublicGitSource(args.repositoryUrl)); // The daemon PublicGit adapter is wired in the Daemon phase. throw new Error("PublicGit import is not yet wired to the daemon adapter"); }, }); // --------------------------------------------------------------------------- // Public mutation: put context text (paste/upload) // --------------------------------------------------------------------------- export const putText = internalMutation({ args: { projectId: v.id("projects"), kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), content: v.string(), origin: v.union(v.literal("paste"), v.literal("upload")), }, handler: async (ctx, args) => { const { userId } = await requireCurrentOrganization(ctx); const store = makeProjectMutationStore(ctx); return store.putContext(userId, args.projectId, { _tag: "UserText" as const, content: args.content, kind: args.kind, origin: args.origin, }); }, });