245 lines
7.4 KiB
TypeScript
245 lines
7.4 KiB
TypeScript
import {
|
|
decodePublicGitImportResult,
|
|
preparePublicGitSource,
|
|
type ProjectImportOutcome,
|
|
type ProjectView,
|
|
type PublicGitImportResult,
|
|
type PreparedPublicGitSource,
|
|
} from "@code/primitives/project";
|
|
import { v } from "convex/values";
|
|
import { Effect } from "effect";
|
|
|
|
import { internal } from "./_generated/api";
|
|
import type { Id } from "./_generated/dataModel";
|
|
import { action, internalMutation, query } from "./_generated/server";
|
|
import { createInitialArtifacts } from "./artifactModel";
|
|
import { requireAuthUserId, requireCurrentOrganization } from "./authz";
|
|
import {
|
|
makeProjectMutationStore,
|
|
makeProjectQueryStore,
|
|
persistPublicGitImportTransaction,
|
|
} from "./projectStore";
|
|
import { inspectPublicGit } from "./publicGit";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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<ProjectImportOutcome> => {
|
|
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<ProjectView[]> => {
|
|
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<ProjectView | null> => {
|
|
const { userId } = await requireCurrentOrganization(ctx);
|
|
const store = makeProjectQueryStore(ctx);
|
|
return store.getProject(userId, args.projectId);
|
|
},
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public action: import a supported public Git repository
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const importPublicGit = action({
|
|
args: { repositoryUrl: v.string() },
|
|
handler: async (ctx, args): Promise<ProjectImportOutcome> => {
|
|
const userId = await requireAuthUserId(ctx);
|
|
const source = await Effect.runPromise(
|
|
preparePublicGitSource(args.repositoryUrl)
|
|
);
|
|
const remote = await inspectPublicGit(source);
|
|
const validatedRemote = await Effect.runPromise(
|
|
decodePublicGitImportResult(remote)
|
|
);
|
|
return await ctx.runMutation(internal.projects.persistPublicGitImport, {
|
|
remote: {
|
|
defaultBranch: validatedRemote.defaultBranch,
|
|
documents: validatedRemote.documents.map((document) => ({
|
|
...document,
|
|
})),
|
|
warnings: validatedRemote.warnings.map((warning) => ({ ...warning })),
|
|
},
|
|
source,
|
|
userId,
|
|
});
|
|
},
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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,
|
|
});
|
|
},
|
|
});
|