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
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions apps/dashboard/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -15,6 +16,7 @@ export default function Page() {
</div>
</div>
</div>
<GenerateToken />
</>
);
}
19 changes: 19 additions & 0 deletions apps/dashboard/src/components/generate-token.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client"
import { useAuth } from '@clerk/nextjs';


export function GenerateToken() {
const { getToken } = useAuth();
const handleClick = async () => {
const token = await getToken({ template: 'dashboard' });
console.log(token);
};


return (
<button onClick={() => { handleClick().catch(console.log) }}>
Generate Token
</button>
);

}
18 changes: 18 additions & 0 deletions apps/dashboard/src/components/user-test.tsx
Original file line number Diff line number Diff line change
@@ -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 <pre>{JSON.stringify(user, null, 2)}</pre>;
}

return <div>Not signed in</div>;
}


7 changes: 7 additions & 0 deletions apps/extract-stack/.sst/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,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 {
Expand Down
9 changes: 6 additions & 3 deletions apps/extract-stack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@
"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",
"dependencies": {
"@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",
Expand All @@ -29,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"
}
}
2 changes: 2 additions & 0 deletions apps/extract-stack/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
71 changes: 63 additions & 8 deletions apps/extract-stack/src/extract-repository.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,116 @@
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';
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 });

const db = drizzle(client);

const event = defineEvent(extractRepositoryEvent);

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 ?");

return userOauthAccessTokenPayload.token;
}

const context: Context<GetRepositorySourceControl, GetRepositoryEntities> = {
entities: {
repositories,
namespaces,
},
integrations: {
sourceControl: new GitlabSourceControl(Config.GITLAB_TOKEN),
sourceControl: null,
},
db,
};

const contextSchema = z.object({
authorizer: z.object({
jwt: z.object({
claims: z.object({
sub: z.string(),
}),
}),
}),
});

type CTX = z.infer<typeof contextSchema>;

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<typeof inputSchema>;

export const handler: APIGatewayProxyHandlerV2 = async (apiGatewayEvent) => {
export const handler = ApiHandler(async (ev) => {

const body = useJsonBody() as unknown;

let lambdaContext: CTX;

try {
lambdaContext = contextSchema.parse(ev.requestContext);
} catch (error) {
return {
statusCode: 401,
body: JSON.stringify({ error: (error as Error).message }),
};
}

let input: Input;
let sourceControlAccessToken: string;

try {
input = inputSchema.parse(apiGatewayEvent);
input = inputSchema.parse(body);

} catch (error) {
return {
statusCode: 400,
body: JSON.stringify({ error: (error as Error).message }),
};
}

const { repositoryId, repositoryName, namespaceName } = input;
const { sub } = lambdaContext.authorizer.jwt.claims;


const { repositoryId, repositoryName, namespaceName, sourceControl } = input;

try {
sourceControlAccessToken = await fetchSourceControlAccessToken(sub, `oauth_${sourceControl}`);
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: (error as Error).message }),
}
}

if (sourceControl === "gitlab") {
context.integrations.sourceControl = new GitlabSourceControl(sourceControlAccessToken);
} else if (sourceControl === "github") {
context.integrations.sourceControl = new GitHubSourceControl(sourceControlAccessToken);
}

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,
body: JSON.stringify({})
};
}
});
24 changes: 22 additions & 2 deletions apps/extract-stack/src/stack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Api, EventBus, Queue } 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", {
Expand All @@ -16,15 +17,34 @@ 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 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, queue],
bind: [bus, DATABASE_URL, DATABASE_AUTH_TOKEN, GITLAB_TOKEN, CLERK_SECRET_KEY, queue],
},
},
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",
"POST /start": "src/extract-repository.handler",
},
});

Expand Down
10 changes: 0 additions & 10 deletions apps/test-stack/package.json

This file was deleted.

Loading