Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function CreateTaskDialog({
setIsCreating(true);

try {
const result = await apiTrpcClient.task.createFromUi.mutate({
const result = await apiTrpcClient.task.create.mutate({
title: title.trim(),
description: description.trim() || null,
statusId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,6 @@ function createOrgCollections(organizationId: string): OrgCollections {
columnMapper,
},
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const item = transaction.mutations[0].modified;
const result = await apiClient.task.create.mutate(item);
return { txid: result.txid };
},
onUpdate: async ({ transaction }) => {
const { original, changes } = transaction.mutations[0];
const result = await apiClient.task.update.mutate({
Expand Down
5 changes: 0 additions & 5 deletions apps/mobile/lib/collections/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ function createOrgCollections(organizationId: string): OrgCollections {
columnMapper,
},
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const item = transaction.mutations[0].modified;
const result = await apiClient.task.create.mutate(item);
return { txid: result.txid };
},
onUpdate: async ({ transaction }) => {
const { original, changes } = transaction.mutations[0];
const result = await apiClient.task.update.mutate({
Expand Down
32 changes: 20 additions & 12 deletions apps/web/src/app/cli/authorize/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { auth } from "@superset/auth/server";
import { headers } from "next/headers";
import Image from "next/image";
import { redirect } from "next/navigation";

import { env } from "@/env";
import { api } from "@/trpc/server";
Expand All @@ -11,23 +10,35 @@ interface CliAuthorizePageProps {
searchParams: Promise<Record<string, string>>;
}

function isLoopbackRedirectUri(value: string): boolean {
let parsed: URL;
try {
parsed = new URL(value);
} catch {
return false;
}
if (parsed.protocol !== "http:") return false;
if (parsed.username !== "" || parsed.password !== "") return false;
return parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost";
}

export default async function CliAuthorizePage({
searchParams,
}: CliAuthorizePageProps) {
const session = await auth.api.getSession({
headers: await headers(),
});

const params = await searchParams;

if (!session) {
const returnUrl = `/cli/authorize?${new URLSearchParams(params).toString()}`;
redirect(`/sign-in?redirect=${encodeURIComponent(returnUrl)}`);
// Defensive — middleware should have caught this.
return null;
}

const { state, redirect_uri } = params;
const params = await searchParams;
const state = params.state;
const redirectUri = params.redirect_uri;

if (!state || !redirect_uri) {
if (!state || !redirectUri) {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-muted-foreground">
Expand All @@ -37,10 +48,7 @@ export default async function CliAuthorizePage({
);
}

if (
!redirect_uri.startsWith("http://127.0.0.1:") &&
!redirect_uri.startsWith("http://localhost:")
) {
if (!isLoopbackRedirectUri(redirectUri)) {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-destructive">
Expand Down Expand Up @@ -69,7 +77,7 @@ export default async function CliAuthorizePage({
<main className="flex flex-1 items-center justify-center">
<CliAuthorizeForm
state={state}
redirectUri={redirect_uri}
redirectUri={redirectUri}
userName={session.user.name}
organizations={organizations.map((organization) => ({
id: organization.id,
Expand Down
9 changes: 4 additions & 5 deletions apps/web/src/app/oauth/consent/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { auth } from "@superset/auth/server";
import { db } from "@superset/db/client";
import { headers } from "next/headers";
import Image from "next/image";
import { redirect } from "next/navigation";

import { env } from "@/env";
import { api } from "@/trpc/server";
Expand All @@ -18,13 +17,13 @@ export default async function ConsentPage({ searchParams }: ConsentPageProps) {
});

if (!session) {
const params = await searchParams;
const returnUrl = `/oauth/consent?${new URLSearchParams(params).toString()}`;
redirect(`/sign-in?redirect=${encodeURIComponent(returnUrl)}`);
// Defensive — middleware should have caught this.
return null;
}

const params = await searchParams;
const { client_id, scope } = params;
const client_id = params.client_id;
const scope = params.scope;

if (!client_id) {
return (
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export default async function proxy(req: NextRequest) {
}

if (!session && !isPublicRoute(pathname)) {
return NextResponse.redirect(new URL("/sign-in", req.url));
const signInUrl = new URL("/sign-in", req.url);
signInUrl.searchParams.set("redirect", pathname + req.nextUrl.search);
return NextResponse.redirect(signInUrl);
}

return NextResponse.next();
Expand Down
11 changes: 10 additions & 1 deletion packages/host-service/src/trpc/router/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ import {

export const projectRouter = router({
list: protectedProcedure.query(({ ctx }) => {
return ctx.db.select({ id: projects.id }).from(projects).all();
return ctx.db
.select({
id: projects.id,
repoPath: projects.repoPath,
repoOwner: projects.repoOwner,
repoName: projects.repoName,
repoUrl: projects.repoUrl,
})
.from(projects)
.all();
}),

get: protectedProcedure
Expand Down
45 changes: 42 additions & 3 deletions packages/trpc/src/router/automation/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
automations,
type SelectSubscription,
v2Hosts,
v2Projects,
v2UsersHosts,
v2Workspaces,
} from "@superset/db/schema";
Expand Down Expand Up @@ -92,11 +93,12 @@ async function verifyHostAccess(
async function verifyWorkspaceInOrg(
organizationId: string,
workspaceId: string,
): Promise<void> {
): Promise<{ id: string; projectId: string }> {
const [workspace] = await db
.select({
id: v2Workspaces.id,
organizationId: v2Workspaces.organizationId,
projectId: v2Workspaces.projectId,
})
.from(v2Workspaces)
.where(eq(v2Workspaces.id, workspaceId))
Expand All @@ -108,6 +110,22 @@ async function verifyWorkspaceInOrg(
message: "Workspace not found",
});
}
return { id: workspace.id, projectId: workspace.projectId };
}

async function verifyProjectInOrg(organizationId: string, projectId: string) {
const [project] = await db
.select({ id: v2Projects.id, organizationId: v2Projects.organizationId })
.from(v2Projects)
.where(eq(v2Projects.id, projectId))
.limit(1);

if (!project || project.organizationId !== organizationId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Project not found",
});
}
}

async function getAutomationForUser(
Expand Down Expand Up @@ -192,8 +210,29 @@ export const automationRouter = {
input.targetHostId,
);
}

let v2ProjectId = input.v2ProjectId;
if (input.v2WorkspaceId) {
await verifyWorkspaceInOrg(organizationId, input.v2WorkspaceId);
const workspace = await verifyWorkspaceInOrg(
organizationId,
input.v2WorkspaceId,
);
if (v2ProjectId && v2ProjectId !== workspace.projectId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "v2ProjectId does not match the workspace's project",
});
}
v2ProjectId = workspace.projectId;
} else if (v2ProjectId) {
await verifyProjectInOrg(organizationId, v2ProjectId);
}

if (!v2ProjectId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "v2ProjectId required when v2WorkspaceId is not provided",
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const dtstart = input.dtstart ?? new Date();
Expand All @@ -212,7 +251,7 @@ export const automationRouter = {
prompt: input.prompt,
agentConfig: input.agentConfig,
targetHostId: input.targetHostId ?? null,
v2ProjectId: input.v2ProjectId,
v2ProjectId,
v2WorkspaceId: input.v2WorkspaceId ?? null,
rrule: input.rrule,
dtstart,
Expand Down
29 changes: 17 additions & 12 deletions packages/trpc/src/router/automation/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,23 @@ const rruleBody = z
.max(500)
.describe("RFC 5545 RRULE body, no DTSTART prefix");

export const createAutomationSchema = z.object({
name: z.string().min(1).max(200),
prompt: z.string().min(1).max(20_000),
agentConfig: agentConfigSchema,
targetHostId: z.string().min(1).nullish(),
v2ProjectId: z.string().uuid(),
v2WorkspaceId: z.string().uuid().nullish(),
rrule: rruleBody,
dtstart: z.coerce.date().optional(),
timezone: iana,
mcpScope: z.array(z.string()).default([]),
});
export const createAutomationSchema = z
.object({
name: z.string().min(1).max(200),
prompt: z.string().min(1).max(20_000),
agentConfig: agentConfigSchema,
targetHostId: z.string().min(1).nullish(),
v2ProjectId: z.string().uuid().optional(),
v2WorkspaceId: z.string().uuid().nullish(),
rrule: rruleBody,
dtstart: z.coerce.date().optional(),
timezone: iana,
mcpScope: z.array(z.string()).default([]),
})
.refine((input) => input.v2ProjectId || input.v2WorkspaceId, {
message: "Provide v2ProjectId or v2WorkspaceId",
path: ["v2ProjectId"],
});

export const updateAutomationSchema = z.object({
id: z.string().uuid(),
Expand Down
40 changes: 40 additions & 0 deletions packages/trpc/src/router/host/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,46 @@ import { z } from "zod";
import { jwtProcedure, protectedProcedure } from "../../trpc";

export const hostRouter = {
list: jwtProcedure
.input(z.object({ organizationId: z.string().uuid() }))
.query(async ({ ctx, input }) => {
if (!ctx.organizationIds.includes(input.organizationId)) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Not a member of this organization",
});
}

const rows = await db
.select({
machineId: v2Hosts.machineId,
name: v2Hosts.name,
isOnline: v2Hosts.isOnline,
organizationId: v2Hosts.organizationId,
})
.from(v2Hosts)
.innerJoin(
v2UsersHosts,
and(
eq(v2UsersHosts.organizationId, v2Hosts.organizationId),
eq(v2UsersHosts.hostId, v2Hosts.machineId),
),
)
.where(
and(
eq(v2Hosts.organizationId, input.organizationId),
eq(v2UsersHosts.userId, ctx.userId),
),
);

return rows.map((row) => ({
id: row.machineId,
name: row.name,
online: row.isOnline,
organizationId: row.organizationId,
}));
}),

ensure: jwtProcedure
.input(
z.object({
Expand Down
32 changes: 15 additions & 17 deletions packages/trpc/src/router/task/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,6 @@ import { taskPriorityValues } from "@superset/db/enums";
import { z } from "zod";

export const createTaskSchema = z.object({
slug: z.string().min(1),
title: z.string().min(1),
description: z.string().nullish(),
statusId: z.string().uuid(),
priority: z.enum(taskPriorityValues).default("none"),

organizationId: z.string().uuid(),
assigneeId: z.string().uuid().nullish(),
branch: z.string().nullish(),
estimate: z.number().int().positive().nullish(),
dueDate: z.coerce.date().nullish(),
labels: z.array(z.string()).nullish(),
});

export const createTaskFromUiSchema = z.object({
title: z.string().min(1),
description: z.string().nullish(),
statusId: z.string().uuid().nullish(),
Expand All @@ -33,11 +18,24 @@ export const updateTaskSchema = z.object({
description: z.string().nullish(),
statusId: z.string().uuid().optional(),
priority: z.enum(taskPriorityValues).optional(),

assigneeId: z.string().uuid().nullish(),
branch: z.string().nullish(),
prUrl: z.string().url().nullish(),
estimate: z.number().int().positive().nullish(),
dueDate: z.coerce.date().nullish(),
labels: z.array(z.string()).nullish(),
// Deprecated: accepted-but-ignored. Drop in CLI-vNext cleanup PR.
branch: z.string().nullish(),
});

export const taskListInputSchema = z
.object({
statusId: z.string().uuid().nullish(),
priority: z.enum(taskPriorityValues).nullish(),
assigneeId: z.string().uuid().nullish(),
assigneeMe: z.boolean().nullish(),
creatorMe: z.boolean().nullish(),
search: z.string().min(1).nullish(),
limit: z.number().int().positive().max(500).default(50),
offset: z.number().int().nonnegative().default(0),
})
.nullish();
Loading
Loading