-
Notifications
You must be signed in to change notification settings - Fork 53
[Abandoned] Github integration #651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2462eaa
21b9fbb
c8e28af
b29fa26
b39832d
d5ed52e
9383e28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import { db } from "@superset/db/client"; | ||
| import { githubInstallations } from "@superset/db/schema"; | ||
| import { Client } from "@upstash/qstash"; | ||
| import { z } from "zod"; | ||
|
|
||
| import { env } from "@/env"; | ||
| import { githubApp } from "../octokit"; | ||
|
|
||
| const qstash = new Client({ token: env.QSTASH_TOKEN }); | ||
|
|
||
| const stateSchema = z.object({ | ||
| organizationId: z.string().min(1), | ||
| userId: z.string().min(1), | ||
| }); | ||
|
|
||
| /** | ||
| * Callback handler for GitHub App installation. | ||
| * GitHub redirects here after the user installs/configures the app. | ||
| */ | ||
| export async function GET(request: Request) { | ||
| const url = new URL(request.url); | ||
| const installationId = url.searchParams.get("installation_id"); | ||
| const setupAction = url.searchParams.get("setup_action"); | ||
| const state = url.searchParams.get("state"); | ||
|
|
||
| if (setupAction === "cancel") { | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=installation_cancelled`, | ||
| ); | ||
| } | ||
|
|
||
| if (!installationId || !state) { | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=missing_params`, | ||
| ); | ||
| } | ||
|
|
||
| const parsed = stateSchema.safeParse( | ||
| JSON.parse(Buffer.from(state, "base64url").toString("utf-8")), | ||
| ); | ||
|
|
||
| if (!parsed.success) { | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=invalid_state`, | ||
| ); | ||
| } | ||
|
Comment on lines
+38
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the Proposed fix- const parsed = stateSchema.safeParse(
- JSON.parse(Buffer.from(state, "base64url").toString("utf-8")),
- );
+ let stateData: unknown;
+ try {
+ stateData = JSON.parse(Buffer.from(state, "base64url").toString("utf-8"));
+ } catch {
+ return Response.redirect(
+ `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=invalid_state`,
+ );
+ }
+
+ const parsed = stateSchema.safeParse(stateData);🤖 Prompt for AI Agents |
||
|
|
||
| const { organizationId, userId } = parsed.data; | ||
|
|
||
| try { | ||
| const octokit = await githubApp.getInstallationOctokit( | ||
| Number(installationId), | ||
| ); | ||
|
|
||
| const installationResult = await octokit | ||
| .request("GET /app/installations/{installation_id}", { | ||
| installation_id: Number(installationId), | ||
| }) | ||
| .catch((error: Error) => { | ||
| console.error("[github/callback] Failed to fetch installation:", error); | ||
| return null; | ||
| }); | ||
|
|
||
| if (!installationResult) { | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=installation_fetch_failed`, | ||
| ); | ||
| } | ||
|
|
||
| const installation = installationResult.data; | ||
|
|
||
| // Extract account info - account can be User or Enterprise | ||
| const account = installation.account; | ||
| const accountLogin = | ||
| account && "login" in account ? account.login : (account?.name ?? ""); | ||
| const accountType = | ||
| account && "type" in account ? account.type : "Organization"; | ||
|
|
||
| // Save the installation to our database | ||
| const [savedInstallation] = await db | ||
| .insert(githubInstallations) | ||
| .values({ | ||
| organizationId, | ||
| connectedByUserId: userId, | ||
| installationId: String(installation.id), | ||
| accountLogin, | ||
| accountType, | ||
| permissions: installation.permissions as Record<string, string>, | ||
| }) | ||
| .onConflictDoUpdate({ | ||
| target: [githubInstallations.organizationId], | ||
| set: { | ||
| connectedByUserId: userId, | ||
| installationId: String(installation.id), | ||
| accountLogin, | ||
| accountType, | ||
| permissions: installation.permissions as Record<string, string>, | ||
| suspended: false, | ||
| suspendedAt: null, // Clear suspension if reinstalling | ||
| updatedAt: new Date(), | ||
| }, | ||
| }) | ||
| .returning(); | ||
|
|
||
| if (!savedInstallation) { | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=save_failed`, | ||
| ); | ||
| } | ||
|
|
||
| // Queue initial sync job | ||
| try { | ||
| await qstash.publishJSON({ | ||
| url: `${env.NEXT_PUBLIC_API_URL}/api/integrations/github/jobs/initial-sync`, | ||
| body: { | ||
| installationDbId: savedInstallation.id, | ||
| organizationId, | ||
| }, | ||
| retries: 3, | ||
| }); | ||
| } catch (error) { | ||
| console.error("[github/callback] Failed to queue initial sync job:", error); | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?warning=sync_queue_failed`, | ||
| ); | ||
| } | ||
|
|
||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?success=github_installed`, | ||
| ); | ||
| } catch (error) { | ||
| console.error("[github/callback] Unexpected error:", error); | ||
| return Response.redirect( | ||
| `${env.NEXT_PUBLIC_WEB_URL}/settings/integrations?error=unexpected`, | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { auth } from "@superset/auth/server"; | ||
| import { db } from "@superset/db/client"; | ||
| import { members } from "@superset/db/schema"; | ||
| import { and, eq } from "drizzle-orm"; | ||
|
|
||
| import { env } from "@/env"; | ||
|
|
||
| export async function GET(request: Request) { | ||
| const session = await auth.api.getSession({ headers: request.headers }); | ||
|
|
||
| if (!session?.user) { | ||
| return Response.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| const url = new URL(request.url); | ||
| const organizationId = url.searchParams.get("organizationId"); | ||
|
|
||
| if (!organizationId) { | ||
| return Response.json( | ||
| { error: "Missing organizationId parameter" }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
|
|
||
| const membership = await db.query.members.findFirst({ | ||
| where: and( | ||
| eq(members.organizationId, organizationId), | ||
| eq(members.userId, session.user.id), | ||
| ), | ||
| }); | ||
|
|
||
| if (!membership) { | ||
| return Response.json( | ||
| { error: "User is not a member of this organization" }, | ||
| { status: 403 }, | ||
| ); | ||
| } | ||
|
|
||
| if (!env.GITHUB_APP_ID) { | ||
| return Response.json( | ||
| { error: "GitHub App not configured" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| const state = Buffer.from( | ||
| JSON.stringify({ organizationId, userId: session.user.id }), | ||
| ).toString("base64url"); | ||
|
|
||
| const installUrl = new URL( | ||
| `https://github.com/apps/superset-app/installations/new`, | ||
| ); | ||
| installUrl.searchParams.set("state", state); | ||
|
|
||
| return Response.redirect(installUrl.toString()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
@octokit/app npm package latest version💡 Result:
Latest @octokit/app on npm: 16.1.0. [1]
Sources:
🌐 Web query:
@octokit/rest npm package latest version💡 Result:
The latest published version of @octokit/rest is 22.0.0. [1][2]
Sources:
🌐 Web query:
@octokit/webhooks npm package latest version💡 Result:
Latest published version (npm) of @octokit/webhooks: 13.8.1. [1]
Sources:
[1] npm package page for @octokit/webhooks.
Update Octokit dependencies to use published versions.
The specified versions do not exist on npm:
@octokit/app@^16.1.2— latest published is 16.1.0@octokit/rest@^22.0.1— latest published is 22.0.0@octokit/webhooks@^14.2.0— latest published is 13.8.1Update to versions that are actually available on npm to ensure the dependencies can be installed.
🤖 Prompt for AI Agents