92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { v } from "convex/values";
|
|
|
|
import { internalQuery } from "./_generated/server";
|
|
|
|
/**
|
|
* Query the project source metadata the coordinator needs (clone URL, default
|
|
* branch, org). Internal so the Node action can read it without touching the DB.
|
|
*
|
|
* Lives outside the `"use node"` projectSetup module because Convex forbids
|
|
* query/mutation definitions in Node-runtime modules.
|
|
*/
|
|
export const getProjectSource = internalQuery({
|
|
args: { projectId: v.id("projects") },
|
|
handler: async (ctx, args) => {
|
|
const project = await ctx.db.get(args.projectId);
|
|
if (!project) {
|
|
return null;
|
|
}
|
|
return {
|
|
defaultBranch: project.defaultBranch ?? null,
|
|
name: project.name,
|
|
organizationId: project.organizationId,
|
|
sourceUrl: project.sourceUrl,
|
|
};
|
|
},
|
|
});
|
|
|
|
/** Authorize a retry and read its runtime within the query context. Actions do
|
|
* not have direct database access, so the authenticated user ID is derived by
|
|
* the action and verified against the project's organization here. */
|
|
export const getRetryableRuntime = internalQuery({
|
|
args: { projectId: v.id("projects"), userId: v.string() },
|
|
handler: async (ctx, args) => {
|
|
const project = await ctx.db.get(args.projectId);
|
|
if (!project) {
|
|
throw new Error("Project not found");
|
|
}
|
|
const membership = await ctx.db
|
|
.query("organizationMembers")
|
|
.withIndex("by_organizationId_and_userId", (q) =>
|
|
q.eq("organizationId", project.organizationId).eq("userId", args.userId)
|
|
)
|
|
.unique();
|
|
if (!membership) {
|
|
throw new Error("Organization membership required");
|
|
}
|
|
const runtime = await ctx.db
|
|
.query("projectRuntimes")
|
|
.withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
|
|
.unique();
|
|
return { organizationId: project.organizationId, runtime };
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Read-only project setup status for the setup screen: latest runtime row plus
|
|
* the setup-scoped events. Auth-gated to project members.
|
|
*/
|
|
export const getStatus = internalQuery({
|
|
args: { projectId: v.id("projects") },
|
|
handler: async (ctx, args) => {
|
|
const project = await ctx.db.get(args.projectId);
|
|
if (!project) {
|
|
return { events: [], runtime: null };
|
|
}
|
|
const runtime = await ctx.db
|
|
.query("projectRuntimes")
|
|
.withIndex("by_projectId", (q) => q.eq("projectId", args.projectId))
|
|
.unique();
|
|
const events = await ctx.db
|
|
.query("events")
|
|
.withIndex("by_organizationId_and_projectId_and_recordedAt", (q) =>
|
|
q
|
|
.eq("organizationId", project.organizationId)
|
|
.eq("projectId", args.projectId)
|
|
)
|
|
.order("desc")
|
|
.filter((q) =>
|
|
q.or(
|
|
q.eq(q.field("type"), "project.setup.creating_vm"),
|
|
q.eq(q.field("type"), "project.setup.cloning"),
|
|
q.eq(q.field("type"), "project.setup.checking_repository"),
|
|
q.eq(q.field("type"), "project.ready"),
|
|
q.eq(q.field("type"), "project.setup.failed"),
|
|
q.eq(q.field("type"), "project.setup.started")
|
|
)
|
|
)
|
|
.take(50);
|
|
return { events, runtime };
|
|
},
|
|
});
|