From 1d78940a616d187ea54231369723eb479a509873 Mon Sep 17 00:00:00 2001 From: dejan-crocoder Date: Fri, 4 Aug 2023 15:55:20 +0200 Subject: [PATCH 01/13] feat: clerk integration --- apps/extract-stack/.sst/types/index.ts | 7 ++ apps/extract-stack/package.json | 1 + apps/extract-stack/src/extract-repository.ts | 43 ++++++++---- apps/extract-stack/src/stack.ts | 3 +- package-lock.json | 65 ++++++++++++++++--- .../source-control/src/gitlab/index.ts | 2 +- 6 files changed, 99 insertions(+), 22 deletions(-) diff --git a/apps/extract-stack/.sst/types/index.ts b/apps/extract-stack/.sst/types/index.ts index df49bfe49..470f8bbf1 100644 --- a/apps/extract-stack/.sst/types/index.ts +++ b/apps/extract-stack/.sst/types/index.ts @@ -32,6 +32,13 @@ declare module "sst/node/config" { value: string; } } +}import "sst/node/config"; +declare module "sst/node/config" { + export interface SecretResources { + "CLERK_SECRET_KEY": { + value: string; + } + } }import "sst/node/api"; declare module "sst/node/api" { export interface ApiResources { diff --git a/apps/extract-stack/package.json b/apps/extract-stack/package.json index 90e6bff6c..994b041fb 100644 --- a/apps/extract-stack/package.json +++ b/apps/extract-stack/package.json @@ -19,6 +19,7 @@ "@acme/extract-functions": "^1.0.0", "@acme/extract-schema": "^1.0.0", "@acme/source-control": "^1.0.0", + "@clerk/clerk-sdk-node": "^4.12.2", "@libsql/client": "^0.3.1", "@tsconfig/node16": "^16.1.0", "aws-cdk-lib": "2.84.0", diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index d0e5fb90e..39540388d 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -9,6 +9,10 @@ import type { APIGatewayProxyHandlerV2 } from "aws-lambda"; import { z } from "zod"; import { Config } from "sst/node/config"; +import { Clerk } from "@clerk/clerk-sdk-node"; +import type { OAuthProvider } from "@clerk/clerk-sdk-node"; + +const clerkClient = Clerk({secretKey: Config.CLERK_SECRET_KEY}); const client = createClient({ url: Config.DATABASE_URL, authToken: Config.DATABASE_AUTH_TOKEN }); const db = drizzle(client); @@ -16,16 +20,25 @@ const db = drizzle(client); const event = defineEvent(extractRepositoryEvent); -const context: Context = { - entities: { - repositories, - namespaces, - }, - integrations: { - sourceControl: new GitlabSourceControl(Config.GITLAB_TOKEN), - }, - db, -}; +const initContext = async (userId: string, provider: `oauth_${OAuthProvider}`): Promise> => { + const [userOauthAccessTokenPayload, ...rest] = await clerkClient.users.getUserOauthAccessToken(userId, provider); + + if (!userOauthAccessTokenPayload) throw new Error("Failed to get token"); + if (rest.length !== 0) throw new Error("wtf ?"); + + return { + entities: { + repositories, + namespaces, + }, + integrations: { + sourceControl: new GitlabSourceControl(userOauthAccessTokenPayload.token), + }, + db, + + } +} +const lazyContext = initContext('user_2TVwyW6xEViigMUomoCx96I4rQb', 'oauth_gitlab'); const inputSchema = z.object({ repositoryId: z.number(), @@ -36,8 +49,8 @@ const inputSchema = z.object({ type Input = z.infer; export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { - let input: Input; + let context: Context; try { input = inputSchema.parse(apiGatewayEvent); @@ -47,6 +60,14 @@ export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { body: JSON.stringify({ error: (error as Error).message }), }; } + try { + context = await lazyContext; + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify({ error: (error as Error).message }), + } + } const { repositoryId, repositoryName, namespaceName } = input; diff --git a/apps/extract-stack/src/stack.ts b/apps/extract-stack/src/stack.ts index 486f2f257..e52cbf052 100644 --- a/apps/extract-stack/src/stack.ts +++ b/apps/extract-stack/src/stack.ts @@ -12,11 +12,12 @@ export function ExtractStack({ stack }: StackContext) { const DATABASE_URL = new Config.Secret(stack, "DATABASE_URL"); const DATABASE_AUTH_TOKEN = new Config.Secret(stack, "DATABASE_AUTH_TOKEN"); const GITLAB_TOKEN = new Config.Secret(stack, "GITLAB_TOKEN"); + const CLERK_SECRET_KEY = new Config.Secret(stack, "CLERK_SECRET_KEY"); const api = new Api(stack, "ExtractApi", { defaults: { function: { - bind: [bus, DATABASE_URL, DATABASE_AUTH_TOKEN, GITLAB_TOKEN], + bind: [bus, DATABASE_URL, DATABASE_AUTH_TOKEN, GITLAB_TOKEN, CLERK_SECRET_KEY], }, }, routes: { diff --git a/package-lock.json b/package-lock.json index 6256e990b..ca41c4342 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "@acme/extract-functions": "^1.0.0", "@acme/extract-schema": "^1.0.0", "@acme/source-control": "^1.0.0", + "@clerk/clerk-sdk-node": "^4.12.2", "@libsql/client": "^0.3.1", "@tsconfig/node16": "^16.1.0", "aws-cdk-lib": "2.84.0", @@ -2576,17 +2577,16 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@clerk/clerk-sdk-node": { - "version": "4.10.15", - "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-4.10.15.tgz", - "integrity": "sha512-fif8iwedLDFgllPLx8xwxA66ICbp7KjsSULqb8mcKXHPnZ/xL17eZSx4puHg4CEGBmOxY6IL5nbRj1+s+TfgqQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-4.12.2.tgz", + "integrity": "sha512-7xYPsLSeGO5XoP0No/9m2dsCMezwtmiYGKOwWzt41ZzJNFlU0rfqYF3VOZEsbtQlc3ZXeU+67ItjoJYrf3kT6A==", "dependencies": { - "@clerk/backend": "^0.24.0", - "@clerk/types": "^3.46.1", + "@clerk/backend": "^0.27.0", + "@clerk/types": "^3.49.0", "@types/cookies": "0.7.7", "@types/express": "4.17.14", "@types/node-fetch": "2.6.2", "camelcase-keys": "6.2.2", - "cookie": "0.5.0", "snakecase-keys": "3.2.1", "tslib": "2.4.1" }, @@ -2594,6 +2594,42 @@ "node": ">=14" } }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@clerk/backend": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-0.27.0.tgz", + "integrity": "sha512-Sj541JrpqAn1A/UwdyDBxFV3stq2A/Pe/8HdPTG3Cct6briPyavfi46O5s1+L3BSvUcKUY+UbM0+8VsoCNFi4w==", + "dependencies": { + "@clerk/types": "^3.49.0", + "@peculiar/webcrypto": "1.4.1", + "@types/node": "16.18.6", + "cookie": "0.5.0", + "deepmerge": "4.2.2", + "node-fetch-native": "1.0.1", + "snakecase-keys": "5.4.4", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@clerk/backend/node_modules/snakecase-keys": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-5.4.4.tgz", + "integrity": "sha512-YTywJG93yxwHLgrYLZjlC75moVEX04LZM4FHfihjHe1FCXm+QaLOFfSf535aXOAd0ArVQMWUAe8ZPm4VtWyXaA==", + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^2.5.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@types/node": { + "version": "16.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.6.tgz", + "integrity": "sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==" + }, "node_modules/@clerk/clerk-sdk-node/node_modules/snakecase-keys": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.2.1.tgz", @@ -2611,6 +2647,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, + "node_modules/@clerk/clerk-sdk-node/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@clerk/nextjs": { "version": "4.21.15", "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-4.21.15.tgz", @@ -2651,9 +2698,9 @@ } }, "node_modules/@clerk/types": { - "version": "3.46.1", - "resolved": "https://registry.npmjs.org/@clerk/types/-/types-3.46.1.tgz", - "integrity": "sha512-IA/iSXJJZym4Z6f9OcT9OJayK2opVhxUVJky15aY4iVsJCPScgApBuuzC7N4vfXwA6RI/wGebFbvmsEQ3oI/UA==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-3.49.0.tgz", + "integrity": "sha512-vAx5R/iYfsgIaIDMiDr6ZKQnAneAmRrUVYz6KCtPG6/hnEAnRYhwXpEUi89e5G0BFmuUfSxe/N/Anfc1PNteXQ==", "dependencies": { "csstype": "3.1.1" }, diff --git a/packages/integrations/source-control/src/gitlab/index.ts b/packages/integrations/source-control/src/gitlab/index.ts index d623369f8..a2e217d08 100644 --- a/packages/integrations/source-control/src/gitlab/index.ts +++ b/packages/integrations/source-control/src/gitlab/index.ts @@ -8,7 +8,7 @@ export class GitlabSourceControl implements SourceControl { constructor(token: string) { this.api = new Gitlab({ - token, + oauthToken: token, // camelize: true }); } From 360897855b89499c4254472c8877e16b5e603483 Mon Sep 17 00:00:00 2001 From: dejan-crocoder Date: Mon, 7 Aug 2023 16:11:42 +0200 Subject: [PATCH 02/13] uncouple context init and token retrieval --- apps/extract-stack/src/extract-repository.ts | 36 +++++++++----------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index 39540388d..6a53672c9 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -10,35 +10,21 @@ import { z } from "zod"; import { Config } from "sst/node/config"; import { Clerk } from "@clerk/clerk-sdk-node"; -import type { OAuthProvider } from "@clerk/clerk-sdk-node"; -const clerkClient = Clerk({secretKey: Config.CLERK_SECRET_KEY}); +const clerkClient = Clerk({ secretKey: Config.CLERK_SECRET_KEY }); const client = createClient({ url: Config.DATABASE_URL, authToken: Config.DATABASE_AUTH_TOKEN }); const db = drizzle(client); const event = defineEvent(extractRepositoryEvent); - -const initContext = async (userId: string, provider: `oauth_${OAuthProvider}`): Promise> => { - const [userOauthAccessTokenPayload, ...rest] = await clerkClient.users.getUserOauthAccessToken(userId, provider); - +const fetchGitForgeryAccessToken = async (userId: string, forgeryIdProvider: 'oauth_github' | 'oauth_gitlab') => { + const [userOauthAccessTokenPayload, ...rest] = await clerkClient.users.getUserOauthAccessToken(userId, forgeryIdProvider); if (!userOauthAccessTokenPayload) throw new Error("Failed to get token"); if (rest.length !== 0) throw new Error("wtf ?"); - return { - entities: { - repositories, - namespaces, - }, - integrations: { - sourceControl: new GitlabSourceControl(userOauthAccessTokenPayload.token), - }, - db, - - } + return userOauthAccessTokenPayload.token; } -const lazyContext = initContext('user_2TVwyW6xEViigMUomoCx96I4rQb', 'oauth_gitlab'); const inputSchema = z.object({ repositoryId: z.number(), @@ -50,7 +36,7 @@ type Input = z.infer; export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { let input: Input; - let context: Context; + let gitForgeryAccessToken: string; try { input = inputSchema.parse(apiGatewayEvent); @@ -61,7 +47,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { }; } try { - context = await lazyContext; + gitForgeryAccessToken = await fetchGitForgeryAccessToken('', 'oauth_gitlab'); } catch (error) { return { statusCode: 500, @@ -70,6 +56,16 @@ export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { } const { repositoryId, repositoryName, namespaceName } = input; + const context: Context = { + entities: { + repositories, + namespaces, + }, + integrations: { + sourceControl: new GitlabSourceControl(gitForgeryAccessToken) + }, + db, + } const { repository, namespace } = await getRepository({ externalRepositoryId: repositoryId, repositoryName, namespaceName }, context); From 4af893c25dac4056ca133b5ba4756f3c11bd266b Mon Sep 17 00:00:00 2001 From: David Abram Date: Mon, 7 Aug 2023 23:19:42 +0200 Subject: [PATCH 03/13] init --- apps/extract-stack/package.json | 8 ++-- apps/extract-stack/src/extract-repository.ts | 41 +++++++++++++++++++- apps/extract-stack/src/stack.ts | 19 +++++++++ package-lock.json | 6 ++- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/apps/extract-stack/package.json b/apps/extract-stack/package.json index 994b041fb..34944d644 100644 --- a/apps/extract-stack/package.json +++ b/apps/extract-stack/package.json @@ -7,11 +7,12 @@ "type-check": "tsc --noEmit && echo \"✔ No TypeScript warnings or errors\"", "test": "echo \"Warning: no test specified\"", "lint": "eslint . && echo \"✔ No ESLint warnings or errors\"", - "dev": "sst dev", + "dev": "npm run with-env sst dev", "build": "sst build", "deploy": "sst deploy", "remove": "sst remove", - "console": "sst console" + "console": "sst console", + "with-env": "dotenv -e ../../.env --" }, "author": "", "license": "ISC", @@ -30,6 +31,7 @@ "zod": "^3.21.4" }, "devDependencies": { - "@types/aws-lambda": "^8.10.119" + "@types/aws-lambda": "^8.10.119", + "dotenv-cli": "^7.2.1" } } diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index 6a53672c9..58e432919 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -26,6 +26,29 @@ const fetchGitForgeryAccessToken = async (userId: string, forgeryIdProvider: 'oa return userOauthAccessTokenPayload.token; } +const context: Context = { + entities: { + repositories, + namespaces, + }, + integrations: { + sourceControl: new GitlabSourceControl(""), + }, + db, +}; + +const contextSchema = z.object({ + authorizer: z.object({ + jwt: z.object({ + claims: z.object({ + sub: z.string(), + }), + }), + }), +}); + +type CTX = z.infer; + const inputSchema = z.object({ repositoryId: z.number(), repositoryName: z.string(), @@ -34,12 +57,24 @@ const inputSchema = z.object({ type Input = z.infer; -export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { +export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { + + let lambdaContext: CTX; + + try { + lambdaContext = contextSchema.parse(ctx); + } catch (error) { + return { + statusCode: 403, + body: JSON.stringify({ error: (error as Error).message }), + }; + } + let input: Input; let gitForgeryAccessToken: string; try { - input = inputSchema.parse(apiGatewayEvent); + input = inputSchema.parse(ev); } catch (error) { return { statusCode: 400, @@ -55,6 +90,8 @@ export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => { } } + const { sub } = lambdaContext.authorizer.jwt.claims; + const { repositoryId, repositoryName, namespaceName } = input; const context: Context = { entities: { diff --git a/apps/extract-stack/src/stack.ts b/apps/extract-stack/src/stack.ts index e52cbf052..315cf7005 100644 --- a/apps/extract-stack/src/stack.ts +++ b/apps/extract-stack/src/stack.ts @@ -1,6 +1,7 @@ import { Api, EventBus } from "sst/constructs"; import type { StackContext } from "sst/constructs"; import { Config } from "sst/constructs"; +import { z } from "zod"; export function ExtractStack({ stack }: StackContext) { const bus = new EventBus(stack, "ExtractBus", { @@ -14,12 +15,30 @@ export function ExtractStack({ stack }: StackContext) { const GITLAB_TOKEN = new Config.Secret(stack, "GITLAB_TOKEN"); const CLERK_SECRET_KEY = new Config.Secret(stack, "CLERK_SECRET_KEY"); + const ENVSchema = z.object({ + CLERK_JWT_ISSUER: z.string(), + CLERK_JWT_AUDIENCE: z.string(), + }); + + const ENV = ENVSchema.parse(process.env); + const api = new Api(stack, "ExtractApi", { defaults: { + authorizer: 'JwtAuthorizer', function: { bind: [bus, DATABASE_URL, DATABASE_AUTH_TOKEN, GITLAB_TOKEN, CLERK_SECRET_KEY], }, }, + authorizers: { + JwtAuthorizer: { + type: "jwt", + identitySource: ["$request.header.Authorization"], + jwt: { + issuer: ENV.CLERK_JWT_ISSUER, + audience: [ENV.CLERK_JWT_AUDIENCE], + }, + }, + }, routes: { "POST /gitlab": "src/extract-repository.handler", }, diff --git a/package-lock.json b/package-lock.json index ca41c4342..4597bead1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,8 @@ "zod": "^3.21.4" }, "devDependencies": { - "@types/aws-lambda": "^8.10.119" + "@types/aws-lambda": "^8.10.119", + "dotenv-cli": "^7.2.1" } }, "apps/extract-stack/node_modules/@tsconfig/node16": { @@ -10551,8 +10552,9 @@ }, "node_modules/dotenv-cli": { "version": "7.2.1", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.2.1.tgz", + "integrity": "sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==", "dev": true, - "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "dotenv": "^16.0.0", From 1f4bb1e7046154b9bcf627b0a9161430a2286ae5 Mon Sep 17 00:00:00 2001 From: David Abram Date: Mon, 7 Aug 2023 23:40:42 +0200 Subject: [PATCH 04/13] get sub, set sourceControl --- .../source-control/github/route.ts | 0 .../source-control/gitlab/route.ts | 0 apps/dashboard/src/components/user-test.tsx | 18 +++++++++++ apps/extract-stack/src/extract-repository.ts | 30 +++++++++---------- apps/extract-stack/src/stack.ts | 2 +- 5 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 apps/dashboard/src/app/api/integrations/source-control/github/route.ts create mode 100644 apps/dashboard/src/app/api/integrations/source-control/gitlab/route.ts create mode 100644 apps/dashboard/src/components/user-test.tsx diff --git a/apps/dashboard/src/app/api/integrations/source-control/github/route.ts b/apps/dashboard/src/app/api/integrations/source-control/github/route.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/dashboard/src/app/api/integrations/source-control/gitlab/route.ts b/apps/dashboard/src/app/api/integrations/source-control/gitlab/route.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/dashboard/src/components/user-test.tsx b/apps/dashboard/src/components/user-test.tsx new file mode 100644 index 000000000..3ed20d426 --- /dev/null +++ b/apps/dashboard/src/components/user-test.tsx @@ -0,0 +1,18 @@ +"use client"; +import { useUser } from "@clerk/clerk-react"; + +export default function Home() { + const { isSignedIn, user, isLoaded } = useUser(); + + if (!isLoaded) { + return null; + } + + if (isSignedIn) { + return
{JSON.stringify(user, null, 2)}
; + } + + return
Not signed in
; +} + + diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index 58e432919..34f187dc4 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -1,7 +1,7 @@ import { extractRepositoryEvent, defineEvent } from "./events"; import { getRepository } from "@acme/extract-functions"; import type { Context, GetRepositorySourceControl, GetRepositoryEntities } from "@acme/extract-functions"; -import { GitlabSourceControl } from "@acme/source-control"; +import { GitlabSourceControl, GitHubSourceControl } from "@acme/source-control"; import { repositories, namespaces } from "@acme/extract-schema"; import { createClient } from '@libsql/client'; import { drizzle } from 'drizzle-orm/libsql'; @@ -53,6 +53,7 @@ const inputSchema = z.object({ repositoryId: z.number(), repositoryName: z.string(), namespaceName: z.string(), + sourceControl: z.literal("gitlab").or(z.literal("github")), }); type Input = z.infer; @@ -65,7 +66,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { lambdaContext = contextSchema.parse(ctx); } catch (error) { return { - statusCode: 403, + statusCode: 401, body: JSON.stringify({ error: (error as Error).message }), }; } @@ -81,8 +82,15 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { body: JSON.stringify({ error: (error as Error).message }), }; } + + const { sub } = lambdaContext.authorizer.jwt.claims; + + console.log({ sub }); + + const { repositoryId, repositoryName, namespaceName, sourceControl } = input; + try { - gitForgeryAccessToken = await fetchGitForgeryAccessToken('', 'oauth_gitlab'); + gitForgeryAccessToken = await fetchGitForgeryAccessToken(sub, `oauth_${sourceControl}`); } catch (error) { return { statusCode: 500, @@ -90,18 +98,10 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { } } - const { sub } = lambdaContext.authorizer.jwt.claims; - - const { repositoryId, repositoryName, namespaceName } = input; - const context: Context = { - entities: { - repositories, - namespaces, - }, - integrations: { - sourceControl: new GitlabSourceControl(gitForgeryAccessToken) - }, - db, + if (sourceControl === "gitlab") { + context.integrations.sourceControl = new GitlabSourceControl(gitForgeryAccessToken); + } else if (sourceControl === "github") { + context.integrations.sourceControl = new GitHubSourceControl(gitForgeryAccessToken); } const { repository, namespace } = await getRepository({ externalRepositoryId: repositoryId, repositoryName, namespaceName }, context); diff --git a/apps/extract-stack/src/stack.ts b/apps/extract-stack/src/stack.ts index 315cf7005..792d99d59 100644 --- a/apps/extract-stack/src/stack.ts +++ b/apps/extract-stack/src/stack.ts @@ -40,7 +40,7 @@ export function ExtractStack({ stack }: StackContext) { }, }, routes: { - "POST /gitlab": "src/extract-repository.handler", + "POST /start": "src/extract-repository.handler", }, }); From 70f3caf8f947317b32606799a90cb0ae5972aa34 Mon Sep 17 00:00:00 2001 From: David Abram Date: Mon, 7 Aug 2023 23:41:39 +0200 Subject: [PATCH 05/13] remove test-stack --- apps/test-stack/package.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 apps/test-stack/package.json diff --git a/apps/test-stack/package.json b/apps/test-stack/package.json deleted file mode 100644 index b678fe4db..000000000 --- a/apps/test-stack/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@acme/test-stack", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": {}, - "keywords": [], - "author": "", - "license": "ISC" -} From b62900f02021260a66cded30fb48b2fa05ee3d9f Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 00:05:09 +0200 Subject: [PATCH 06/13] package lock --- package-lock.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4597bead1..5e705c221 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,6 +94,7 @@ "apps/test-stack": { "name": "@acme/test-stack", "version": "1.0.0", + "extraneous": true, "license": "ISC" }, "node_modules/@acme/eslint-config": { @@ -124,10 +125,6 @@ "resolved": "packages/config/tailwind", "link": true }, - "node_modules/@acme/test-stack": { - "resolved": "apps/test-stack", - "link": true - }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.1.tgz", From f1ed3146a57927ce42bda1fa2155d00f43c90e3a Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 00:35:35 +0200 Subject: [PATCH 07/13] add sourceControl and userId to event --- apps/extract-stack/src/events.ts | 2 ++ apps/extract-stack/src/extract-repository.ts | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/extract-stack/src/events.ts b/apps/extract-stack/src/events.ts index 5a79c2ec8..960c34dc9 100644 --- a/apps/extract-stack/src/events.ts +++ b/apps/extract-stack/src/events.ts @@ -9,6 +9,8 @@ const eventBuilder = createEventBuilder({ version: z.number(), timestamp: z.number(), caller: z.string(), + sourceControl: z.literal("github").or(z.literal("gitlab")), + userId: z.string(), }).shape, }); diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index 34f187dc4..7f6ef521a 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -85,8 +85,6 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { const { sub } = lambdaContext.authorizer.jwt.claims; - console.log({ sub }); - const { repositoryId, repositoryName, namespaceName, sourceControl } = input; try { @@ -106,7 +104,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { const { repository, namespace } = await getRepository({ externalRepositoryId: repositoryId, repositoryName, namespaceName }, context); - await event.publish({ repository, namespace }, { caller: 'extract-repository', timestamp: new Date().getTime(), version: 1 }); + await event.publish({ repository, namespace }, { caller: 'extract-repository', timestamp: new Date().getTime(), version: 1, sourceControl, userId: sub }); return { statusCode: 200, From 225fb4dd860baa1d89f2f9d1588a1f1c5bc0e174 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 00:55:27 +0200 Subject: [PATCH 08/13] source control type fixed --- apps/extract-stack/src/extract-repository.ts | 3 ++- packages/functions/extract/src/config.ts | 2 +- packages/functions/extract/src/get-merge-requests.ts | 5 +++++ packages/functions/extract/src/get-repository.ts | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index 7f6ef521a..b407dc75d 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -32,7 +32,7 @@ const context: Context = { namespaces, }, integrations: { - sourceControl: new GitlabSourceControl(""), + sourceControl: null, }, db, }; @@ -85,6 +85,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { const { sub } = lambdaContext.authorizer.jwt.claims; + const { repositoryId, repositoryName, namespaceName, sourceControl } = input; try { diff --git a/packages/functions/extract/src/config.ts b/packages/functions/extract/src/config.ts index 5cce5b3a7..638f8ae92 100644 --- a/packages/functions/extract/src/config.ts +++ b/packages/functions/extract/src/config.ts @@ -13,7 +13,7 @@ export type Entities = { export type Context, E extends Partial> = { integrations: { - sourceControl: SC; + sourceControl: SC | null; }; db: Database; entities: E; diff --git a/packages/functions/extract/src/get-merge-requests.ts b/packages/functions/extract/src/get-merge-requests.ts index 11e4b9ae5..6de930897 100644 --- a/packages/functions/extract/src/get-merge-requests.ts +++ b/packages/functions/extract/src/get-merge-requests.ts @@ -25,6 +25,11 @@ export const getMergeRequests: GetMergeRequestsFunction = async ( { externalRepositoryId, namespaceName, repositoryName, repositoryId }, { integrations, db, entities } ) => { + + if(!integrations.sourceControl) { + throw new Error("Source control integration not configured"); + } + const { mergeRequests, pagination } = await integrations.sourceControl.fetchMergeRequests(externalRepositoryId, namespaceName, repositoryName, repositoryId); const insertedMergeRequests = await db.insert(entities.mergeRequests).values(mergeRequests) diff --git a/packages/functions/extract/src/get-repository.ts b/packages/functions/extract/src/get-repository.ts index 2b2c22aff..7e9ba61f9 100644 --- a/packages/functions/extract/src/get-repository.ts +++ b/packages/functions/extract/src/get-repository.ts @@ -23,6 +23,10 @@ export const getRepository: GetRepositoryFunction = async ( { integrations, db, entities } ) => { + if(!integrations.sourceControl) { + throw new Error("Source control integration not configured"); + } + const { repository, namespace } = await integrations.sourceControl.fetchRepository(externalRepositoryId, namespaceName, repositoryName); const insertedRepository = await db.insert(entities.repositories).values(repository) From d5f97a517ebe5cc99a97797f1e65f8842e8d9522 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 12:47:58 +0200 Subject: [PATCH 09/13] added token generation --- apps/dashboard/src/app/page.tsx | 2 ++ .../src/components/generate-token.tsx | 15 +++++++++++ apps/extract-stack/src/extract-repository.ts | 26 ++++++++++--------- 3 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 apps/dashboard/src/components/generate-token.tsx diff --git a/apps/dashboard/src/app/page.tsx b/apps/dashboard/src/app/page.tsx index d9633ff5c..d1b8a860f 100644 --- a/apps/dashboard/src/app/page.tsx +++ b/apps/dashboard/src/app/page.tsx @@ -1,5 +1,6 @@ import { UserButton, OrganizationSwitcher } from "@clerk/nextjs"; import { MainNav } from "~/components/ui/main-nav"; +import { GenerateToken } from "~/components/generate-token"; export default function Page() { return ( @@ -15,6 +16,7 @@ export default function Page() { + ); } diff --git a/apps/dashboard/src/components/generate-token.tsx b/apps/dashboard/src/components/generate-token.tsx new file mode 100644 index 000000000..1a3edc93b --- /dev/null +++ b/apps/dashboard/src/components/generate-token.tsx @@ -0,0 +1,15 @@ +"use client" +import { useAuth } from '@clerk/nextjs'; + + +export function GenerateToken() { +const { getToken } = useAuth(); + + +return ( + +); + +} diff --git a/apps/extract-stack/src/extract-repository.ts b/apps/extract-stack/src/extract-repository.ts index b407dc75d..b1ad5ffb1 100644 --- a/apps/extract-stack/src/extract-repository.ts +++ b/apps/extract-stack/src/extract-repository.ts @@ -5,11 +5,10 @@ import { GitlabSourceControl, GitHubSourceControl } from "@acme/source-control"; import { repositories, namespaces } from "@acme/extract-schema"; import { createClient } from '@libsql/client'; import { drizzle } from 'drizzle-orm/libsql'; -import type { APIGatewayProxyHandlerV2 } from "aws-lambda"; import { z } from "zod"; import { Config } from "sst/node/config"; - import { Clerk } from "@clerk/clerk-sdk-node"; +import { ApiHandler, useJsonBody } from 'sst/node/api'; const clerkClient = Clerk({ secretKey: Config.CLERK_SECRET_KEY }); const client = createClient({ url: Config.DATABASE_URL, authToken: Config.DATABASE_AUTH_TOKEN }); @@ -18,7 +17,7 @@ const db = drizzle(client); const event = defineEvent(extractRepositoryEvent); -const fetchGitForgeryAccessToken = async (userId: string, forgeryIdProvider: 'oauth_github' | 'oauth_gitlab') => { +const fetchSourceControlAccessToken = async (userId: string, forgeryIdProvider: 'oauth_github' | 'oauth_gitlab') => { const [userOauthAccessTokenPayload, ...rest] = await clerkClient.users.getUserOauthAccessToken(userId, forgeryIdProvider); if (!userOauthAccessTokenPayload) throw new Error("Failed to get token"); if (rest.length !== 0) throw new Error("wtf ?"); @@ -58,12 +57,14 @@ const inputSchema = z.object({ type Input = z.infer; -export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { +export const handler = ApiHandler(async (ev) => { + + const body = useJsonBody() as unknown; - let lambdaContext: CTX; + let lambdaContext: CTX; try { - lambdaContext = contextSchema.parse(ctx); + lambdaContext = contextSchema.parse(ev.requestContext); } catch (error) { return { statusCode: 401, @@ -72,10 +73,11 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { } let input: Input; - let gitForgeryAccessToken: string; + let sourceControlAccessToken: string; try { - input = inputSchema.parse(ev); + input = inputSchema.parse(body); + } catch (error) { return { statusCode: 400, @@ -89,7 +91,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { const { repositoryId, repositoryName, namespaceName, sourceControl } = input; try { - gitForgeryAccessToken = await fetchGitForgeryAccessToken(sub, `oauth_${sourceControl}`); + sourceControlAccessToken = await fetchSourceControlAccessToken(sub, `oauth_${sourceControl}`); } catch (error) { return { statusCode: 500, @@ -98,9 +100,9 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { } if (sourceControl === "gitlab") { - context.integrations.sourceControl = new GitlabSourceControl(gitForgeryAccessToken); + context.integrations.sourceControl = new GitlabSourceControl(sourceControlAccessToken); } else if (sourceControl === "github") { - context.integrations.sourceControl = new GitHubSourceControl(gitForgeryAccessToken); + context.integrations.sourceControl = new GitHubSourceControl(sourceControlAccessToken); } const { repository, namespace } = await getRepository({ externalRepositoryId: repositoryId, repositoryName, namespaceName }, context); @@ -111,4 +113,4 @@ export const handler: APIGatewayProxyHandlerV2 = async (ev, ctx) => { statusCode: 200, body: JSON.stringify({}) }; -} +}); From 252d4559cac0291aff170ec0e996ca9da005ec8b Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 12:53:36 +0200 Subject: [PATCH 10/13] handleClick --- apps/dashboard/src/components/generate-token.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/components/generate-token.tsx b/apps/dashboard/src/components/generate-token.tsx index 1a3edc93b..140a9d533 100644 --- a/apps/dashboard/src/components/generate-token.tsx +++ b/apps/dashboard/src/components/generate-token.tsx @@ -3,13 +3,17 @@ import { useAuth } from '@clerk/nextjs'; export function GenerateToken() { -const { getToken } = useAuth(); + const { getToken } = useAuth(); + const handleClick = async () => { + const token = await getToken({ template: 'dashboard' }); + console.log(token); + }; -return ( - -); + return ( + + ); } From 7dd5c993abd506800b1bf165f1e390f8f9c45f69 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 13:30:23 +0200 Subject: [PATCH 11/13] fix? --- apps/dashboard/src/components/generate-token.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/generate-token.tsx b/apps/dashboard/src/components/generate-token.tsx index 140a9d533..96b3be75c 100644 --- a/apps/dashboard/src/components/generate-token.tsx +++ b/apps/dashboard/src/components/generate-token.tsx @@ -11,7 +11,7 @@ export function GenerateToken() { return ( - ); From 93c1f3bc1273c8b52bfaf7690633cc6a75743e75 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 13:31:37 +0200 Subject: [PATCH 12/13] fix??? --- apps/dashboard/src/components/generate-token.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/generate-token.tsx b/apps/dashboard/src/components/generate-token.tsx index 96b3be75c..ee528b61f 100644 --- a/apps/dashboard/src/components/generate-token.tsx +++ b/apps/dashboard/src/components/generate-token.tsx @@ -11,7 +11,7 @@ export function GenerateToken() { return ( - ); From 1fd20160118cb30ea7a6da2a2d22e30f5ffa2b88 Mon Sep 17 00:00:00 2001 From: David Abram Date: Tue, 8 Aug 2023 13:56:23 +0200 Subject: [PATCH 13/13] please work --- apps/dashboard/src/components/generate-token.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/generate-token.tsx b/apps/dashboard/src/components/generate-token.tsx index ee528b61f..e49fcfb80 100644 --- a/apps/dashboard/src/components/generate-token.tsx +++ b/apps/dashboard/src/components/generate-token.tsx @@ -11,7 +11,7 @@ export function GenerateToken() { return ( - );