Auth flow refactor, project requests, and routing updates
- Add server-side auth token loader (auth.server.ts) with SSR cookie forwarding - Add project-requests client for explicit/signal/existing issue dispatch - Refactor auth layout and app layout for SSR auth gating - Update auth-client and auth-provider for convex token flow - Extend project-issue primitives with request result schema - Wire projectIssues backend mutation for request handling - Update slice-one page and project-workspace hook - Adjust routes (remove unused), Caddy config, and env templates
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { parseAgentEnv } from "@code/env/agent";
|
||||
import { defineAgent } from "@flue/runtime";
|
||||
import type { AgentRouteHandler } from "@flue/runtime";
|
||||
|
||||
import { createFinalizeGiteaLifecycle } from "../actions/finalize-gitea-lifecycle";
|
||||
import { agentOs } from "../sandboxes/agent-os";
|
||||
@@ -9,8 +8,6 @@ import { createProjectTools } from "../tools/project";
|
||||
export const description =
|
||||
"Works one repository issue inside an isolated AgentOS workspace.";
|
||||
|
||||
export const route: AgentRouteHandler = (_context, next) => next();
|
||||
|
||||
export default defineAgent(({ env, id }) => {
|
||||
const { AGENT_MODEL_NAME, AGENT_MODEL_PROVIDER } = parseAgentEnv(env);
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ const createIssueFromSignal = makeFunctionReference<
|
||||
{ readonly signalId: string },
|
||||
{ readonly issueId: string; readonly projectId: string }
|
||||
>("projectIssues:createFromSignal");
|
||||
const getIssue = makeFunctionReference<
|
||||
"query",
|
||||
{ readonly issueId: string },
|
||||
{ readonly issueId: string; readonly projectId: string } | null
|
||||
>("projectIssues:getForDispatch");
|
||||
|
||||
const beginIssue = makeFunctionReference<
|
||||
"mutation",
|
||||
@@ -75,6 +80,14 @@ const createIssueForRequest = async (
|
||||
client: ConvexHttpClient,
|
||||
request: Awaited<ReturnType<typeof decodeRequest>>
|
||||
): Promise<{ readonly issueId: string; readonly projectId: string }> => {
|
||||
if (request.kind === "existing") {
|
||||
const issue = await client.query(getIssue, { issueId: request.issueId });
|
||||
if (!issue) {
|
||||
throw new Error("Project issue not found");
|
||||
}
|
||||
return issue;
|
||||
}
|
||||
|
||||
if (request.kind === "signal") {
|
||||
return client.mutation(createIssueFromSignal, {
|
||||
signalId: request.signalId,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { env } from "@code/env/web";
|
||||
import { convexClient, crossDomainClient } from "@convex-dev/better-auth/client/plugins";
|
||||
import { convexClient } from "@convex-dev/better-auth/client/plugins";
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: env.VITE_CONVEX_SITE_URL,
|
||||
plugins: [convexClient(), crossDomainClient()],
|
||||
baseURL: env.VITE_AUTH_URL,
|
||||
plugins: [convexClient()],
|
||||
});
|
||||
|
||||
@@ -7,8 +7,18 @@ import { authClient } from "./auth-client";
|
||||
|
||||
const convex = new ConvexReactClient(env.VITE_CONVEX_URL, { expectAuth: true });
|
||||
|
||||
export const WebAuthProvider = ({ children }: { children: ReactNode }) => (
|
||||
<ConvexBetterAuthProvider authClient={authClient} client={convex}>
|
||||
export const WebAuthProvider = ({
|
||||
children,
|
||||
initialToken,
|
||||
}: {
|
||||
readonly children: ReactNode;
|
||||
readonly initialToken?: string | null;
|
||||
}) => (
|
||||
<ConvexBetterAuthProvider
|
||||
authClient={authClient}
|
||||
client={convex}
|
||||
initialToken={initialToken}
|
||||
>
|
||||
{children}
|
||||
</ConvexBetterAuthProvider>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expo } from "@better-auth/expo";
|
||||
import { env } from "@code/env/convex";
|
||||
import { createClient } from "@convex-dev/better-auth";
|
||||
import type { GenericCtx } from "@convex-dev/better-auth";
|
||||
import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
|
||||
import { convex } from "@convex-dev/better-auth/plugins";
|
||||
import { betterAuth } from "better-auth/minimal";
|
||||
|
||||
import { components } from "./_generated/api";
|
||||
@@ -25,7 +25,6 @@ const createAuth = (ctx: GenericCtx<DataModel>) =>
|
||||
},
|
||||
plugins: [
|
||||
expo(),
|
||||
crossDomain({ siteUrl }),
|
||||
convex({
|
||||
authConfig,
|
||||
jwksRotateOnTokenGenerationError: true,
|
||||
|
||||
@@ -118,6 +118,18 @@ export const createFromSignal = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
export const getForDispatch = query({
|
||||
args: { issueId: v.id("projectIssues") },
|
||||
handler: async (ctx, args) => {
|
||||
const issue = await ctx.db.get("projectIssues", args.issueId);
|
||||
if (!issue) {
|
||||
return null;
|
||||
}
|
||||
await requireProjectMember(ctx, issue.projectId);
|
||||
return { issueId: issue._id, projectId: issue.projectId };
|
||||
},
|
||||
});
|
||||
|
||||
export const begin = mutation({
|
||||
args: { issueId: v.id("projectIssues") },
|
||||
handler: async (ctx, args) => {
|
||||
|
||||
2
packages/env/src/vite-env.d.ts
vendored
2
packages/env/src/vite-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly [key: string]: string | number | boolean | undefined;
|
||||
readonly VITE_CONVEX_SITE_URL: string;
|
||||
readonly VITE_AUTH_URL: string;
|
||||
readonly VITE_FLUE_URL: string;
|
||||
readonly VITE_CONVEX_URL: string;
|
||||
}
|
||||
|
||||
3
packages/env/src/web.ts
vendored
3
packages/env/src/web.ts
vendored
@@ -8,10 +8,9 @@ const convexUrlSchema = (exampleHost: string) =>
|
||||
|
||||
export const env = createEnv({
|
||||
client: {
|
||||
VITE_CONVEX_SITE_URL: convexUrlSchema("example.convex.site"),
|
||||
VITE_AUTH_URL: z.url(),
|
||||
VITE_CONVEX_URL: convexUrlSchema("example.convex.cloud"),
|
||||
VITE_FLUE_URL: z.url(),
|
||||
VITE_ZOPU_SERVER_URL: z.url(),
|
||||
},
|
||||
clientPrefix: "VITE_",
|
||||
emptyStringAsUndefined: true,
|
||||
|
||||
@@ -29,6 +29,17 @@ describe("project issue primitives", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts an existing issue request without creating a duplicate", () => {
|
||||
const request = Effect.runSync(
|
||||
decodeProjectIssueRequest({
|
||||
issueId: "issue-1",
|
||||
kind: "existing",
|
||||
})
|
||||
);
|
||||
|
||||
expect(request).toEqual({ issueId: "issue-1", kind: "existing" });
|
||||
});
|
||||
|
||||
it("rejects invalid issue drafts before persistence", () => {
|
||||
const error = Effect.runSync(
|
||||
Effect.flip(
|
||||
|
||||
@@ -35,6 +35,11 @@ export const ProjectIssueDraft = Schema.Struct({
|
||||
});
|
||||
export type ProjectIssueDraft = typeof ProjectIssueDraft.Type;
|
||||
|
||||
export const ExistingProjectIssueRequest = Schema.Struct({
|
||||
issueId: ProjectIssueId,
|
||||
kind: Schema.Literal("existing"),
|
||||
});
|
||||
|
||||
export const ExplicitProjectIssueRequest = Schema.Struct({
|
||||
body: Schema.String,
|
||||
kind: Schema.Literal("explicit"),
|
||||
@@ -48,6 +53,7 @@ export const SignalProjectIssueRequest = Schema.Struct({
|
||||
});
|
||||
|
||||
export const ProjectIssueRequest = Schema.Union([
|
||||
ExistingProjectIssueRequest,
|
||||
ExplicitProjectIssueRequest,
|
||||
SignalProjectIssueRequest,
|
||||
]);
|
||||
@@ -186,7 +192,7 @@ export const decodeProjectIssueRequest = (
|
||||
() =>
|
||||
new ProjectIssueRequestError({
|
||||
message:
|
||||
"Use a signal request or an explicit request with projectId, title, and body",
|
||||
"Use an existing issue, signal request, or explicit request with projectId, title, and body",
|
||||
reason: "InvalidInput",
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user