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
4 changes: 3 additions & 1 deletion apps/app-htmx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"esbuild": "^0.19.9"
},
"dependencies": {
"@acme/extract-functions": "*",
"@acme/extract-schema": "*",
"@acme/source-control": "*",
"@acme/super-schema": "*",
"@clerk/fastify": "0.6.30",
Expand All @@ -23,4 +25,4 @@
"fastify": "4.25.2",
"nunjucks": "3.2.4"
}
}
}
45 changes: 45 additions & 0 deletions apps/app-htmx/src/functions/extract-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { GitHubSourceControl, GitlabSourceControl, type SourceControl } from "@acme/source-control"
import type { Tenant } from "@acme/super-schema"
import type { LibSQLDatabase } from "drizzle-orm/libsql"
import { clerkClient } from "@clerk/fastify";
import { tenantDb } from "./tenant-db";

const getUserForgeAccessToken = async (userId: string, forge: "github" | "gitlab") => {
const userTokens = await clerkClient.users.getUserOauthAccessToken(userId, `oauth_${forge}`);
if (userTokens[0] === undefined) throw new Error("no token");
return userTokens[0].token;
}

type BaseExtractContext = {
db: LibSQLDatabase,
integrations: {
sourceControl: SourceControl | null
}
}

type ExtractContextOptions = {
tenant: Tenant;
userId: string;
forge: "github" | "gitlab";
}

export const extractContext: {
<TExtendedContext extends { integrations?: object } | object>(opts: ExtractContextOptions, ctx: TExtendedContext): Promise<TExtendedContext & BaseExtractContext>;
} = async ({ tenant, userId, forge }, ctx) => {
const token = await getUserForgeAccessToken(userId, forge);

let sourceControl = null;
if (forge === "github") sourceControl = new GitHubSourceControl({ auth: token });
else if (forge === "gitlab") sourceControl = new GitlabSourceControl(token);

const ctxIntegrations = ('integrations' in ctx) ? ctx.integrations : {};

return {
...ctx,
db: tenantDb(tenant),
integrations: {
...ctxIntegrations,
sourceControl
}
}
}
42 changes: 0 additions & 42 deletions apps/app-htmx/src/functions/fetch-repository.ts

This file was deleted.

6 changes: 5 additions & 1 deletion apps/app-htmx/src/functions/get-repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import type { LibSQLDatabase } from "drizzle-orm/libsql";

export const getRepositories = async (db: LibSQLDatabase) => {
const repos = await db.select({
id: repositories.id,
forge: repositories.forgeType,
name: repositories.name,
org: namespaces.name,
projectId: repositories.externalId,
}).from(repositories).innerJoin(namespaces, eq(repositories.namespaceId, namespaces.id)).all()

return repos;
return repos.map(repo=>({
...repo,
key: `${repo.forge}-${repo.projectId}`,
}));
}
12 changes: 12 additions & 0 deletions apps/app-htmx/src/functions/tenant-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Tenant } from "@acme/super-schema";
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { AppConfig } from "src/app-config";

export const tenantDb = (tenant:Tenant)=> {
const client = createClient({
url: tenant.dbUrl,
authToken: AppConfig.tenantDatabaseAuthToken
});
return drizzle(client);
}
4 changes: 2 additions & 2 deletions apps/app-htmx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { clerkPlugin } from "@clerk/fastify";
import { Home } from "./pages/page.home.js";
import fastifyFormbody from "@fastify/formbody";
import { SignIn } from "./pages/page.sign-in.js";
import { ExtractRepository } from "./pages/repository/extract.js";
import { RegisterRepository } from "./pages/repository/register.js";
import { AppConfig } from "./app-config.js";
import { StartTransform } from "./pages/start-transform.js";
import { StartExtract } from "./pages/start-extract.js";

AppConfig; // ensure loaded before starting server

Expand All @@ -30,7 +30,7 @@ await fastify.register(fastifyFormbody);

fastify.get('/', Home);
fastify.get('/sign-in',SignIn);
fastify.post('/repository/extract', ExtractRepository);
fastify.post('/extract', StartExtract);
fastify.post('/repository/register', RegisterRepository);
fastify.post('/transform', StartTransform);

Expand Down
4 changes: 1 addition & 3 deletions apps/app-htmx/src/pages/page.home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const Home: RouteHandlerMethod = async (request, reply) => {
const tenantQuery = query.tenant || tenantList.tenantList[0]!.name;
const tenant = tenantList.tenantList.find(t => t.name === tenantQuery);
if (!tenant) return reply.redirect(303, "/");
const targetTenantId = tenant.id;

const db = drizzle(createClient({
url: tenant.dbUrl,
Expand All @@ -46,8 +45,7 @@ export const Home: RouteHandlerMethod = async (request, reply) => {
...page,
...htmx,
...tenantList,
targetTenant: tenant,
targetTenantId,
tenant,
repos,
dates: {
yesterday: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().slice(0, 10),
Expand Down
72 changes: 0 additions & 72 deletions apps/app-htmx/src/pages/repository/extract.ts

This file was deleted.

71 changes: 46 additions & 25 deletions apps/app-htmx/src/pages/repository/register.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { RouteHandlerMethod } from "fastify";
import { getAuth } from "@clerk/fastify"
import { z } from "zod";
import { tryFetchRepository } from "src/functions/fetch-repository";
import { tenantListContext } from "src/context/tenant-list.context";
import { type GetRepositoryFunction, getRepository } from "@acme/extract-functions";
import { namespaces, repositories } from "@acme/extract-schema";
import { extractContext } from "src/functions/extract-context";

const RegisterInput = z.object({
target_tenant_id: z.coerce.number(),
Expand All @@ -12,6 +14,13 @@ const RegisterInput = z.object({
project_id: z.string().optional()
})

const getRepositoryEntitiesContext = {
entities: {
namespaces: namespaces,
repositories: repositories,
},
} satisfies Pick<Parameters<GetRepositoryFunction>[1], 'entities'>;

export const RegisterRepository: RouteHandlerMethod = async (request, reply) => {
const auth = getAuth(request);
if (!auth.userId) return reply.status(404).send(); // hide actions from unauthenticated users
Expand All @@ -21,37 +30,49 @@ export const RegisterRepository: RouteHandlerMethod = async (request, reply) =>
const safeInput = RegisterInput.safeParse(request.body);
if (!safeInput.success) return reply.status(400).send();

const targetTenantId = safeInput.data.target_tenant_id;
const tenant = tenantList.find(tenant => tenant.id === targetTenantId);
if (!tenant) return reply.view("component.log.html", { error: `Invalid target_tenant_id: ${targetTenantId}` });

const { userId } = auth;
const { forge } = safeInput.data;

const input = {
userId: auth.userId,
forge: safeInput.data.forge,
externalRepositoryId: Number(safeInput.data.project_id) || 0,
namespaceName: safeInput.data.owner || "",
repositoryName: safeInput.data.repo || "",
repositoryId: Number(safeInput.data.project_id) || 0
}

const { repository, namespace } = await tryFetchRepository(input);
const getRepositoryContext = await extractContext({ tenant, userId, forge }, getRepositoryEntitiesContext);

if (!repository || !namespace) return reply.view("component.log.html",
safeInput.data.forge === "github" ? { error: `Repository ${input.namespaceName}/${input.repositoryName} not found` }
: { error: `Project ${input.repositoryId} not found` });
try {
const { repository, namespace } = await getRepository(input, getRepositoryContext);

const targetTenantId = safeInput.data.target_tenant_id;
const tenant = tenantList.find(tenant=>tenant.id === targetTenantId);
if (!tenant) return reply.view("component.log.html", { error: `Invalid target_tenant_id: ${targetTenantId}` });
if (repository._createdAt?.getTime() !== repository._updatedAt?.getTime()) {
return reply.view("component.log.html", { log: `Repository : ${namespace.name}/${repository.name} is already registered.` });
}

const repo = {
name: repository.name,
org: namespace.name,
forge: repository.forgeType,
projectId: repository.externalId
}

return reply.view("component.repository.html", {
repo,
targetTenantId,
dates: {
yesterday: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().slice(0, 10),
today: new Date().toISOString().slice(0, 10)
const repo = {
key: `${forge}-${repository.externalId}`,
name: repository.name,
org: namespace.name,
forge,
projectId: repository.externalId,
}
});

return reply.view("component.repository.html", {
repo,
tenant,
dates: {
yesterday: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().slice(0, 10),
today: new Date().toISOString().slice(0, 10)
}
});

} catch (error) {
console.log(error);
const errorMessage = error instanceof Error? error.message : error;
return reply.view("component.log.html", { error: errorMessage });
}

}
Loading