diff --git a/apps/client/src/lib/common.ts b/apps/client/src/lib/common.ts
index 8c2361a0c..6fa60b03b 100644
--- a/apps/client/src/lib/common.ts
+++ b/apps/client/src/lib/common.ts
@@ -3,6 +3,15 @@ import { addToast } from './store';
import Cookies from 'js-cookie';
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
+export function dashify(str: string, options?: any): string {
+ if (typeof str !== 'string') return str;
+ return str
+ .trim()
+ .replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
+ .replace(/^-+|-+$/g, '')
+ .replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
+ .toLowerCase();
+}
export function errorNotification(error: any | { message: string }): void {
if (error instanceof Error) {
console.error(error.message)
diff --git a/apps/client/src/routes/sources/[id]/+layout.svelte b/apps/client/src/routes/sources/[id]/+layout.svelte
new file mode 100644
index 000000000..d8ddbd31a
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/+layout.svelte
@@ -0,0 +1,41 @@
+
+
+{#if id !== 'new' && $appSession.teamId === '0'}
+
+ Delete
+{/if}
+
diff --git a/apps/client/src/routes/sources/[id]/+layout.ts b/apps/client/src/routes/sources/[id]/+layout.ts
new file mode 100644
index 000000000..34c9d07e9
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/+layout.ts
@@ -0,0 +1,35 @@
+import { error } from '@sveltejs/kit';
+import { trpc } from '$lib/store';
+import type { LayoutLoad } from './$types';
+import { redirect } from '@sveltejs/kit';
+
+export const load: LayoutLoad = async ({ params, url }) => {
+ const { pathname } = new URL(url);
+ const { id } = params;
+ try {
+ const source = await trpc.sources.getSourceById.query({ id });
+ if (!source) {
+ throw redirect(307, '/sources');
+ }
+
+ // if (
+ // configurationPhase &&
+ // pathname !== `/applications/${params.id}/configuration/${configurationPhase}`
+ // ) {
+ // throw redirect(302, `/applications/${params.id}/configuration/${configurationPhase}`);
+ // }
+ return {
+ source
+ };
+ } catch (err) {
+ if (err instanceof Error) {
+ throw error(500, {
+ message: 'An unexpected error occurred, please try again later.' + '
' + err.message
+ });
+ }
+
+ throw error(500, {
+ message: 'An unexpected error occurred, please try again later.'
+ });
+ }
+};
diff --git a/apps/client/src/routes/sources/[id]/+page.svelte b/apps/client/src/routes/sources/[id]/+page.svelte
new file mode 100644
index 000000000..f5f9de947
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/+page.svelte
@@ -0,0 +1,17 @@
+
+
+{#if id === 'new'}
+
+{:else}
+
+{/if}
diff --git a/apps/client/src/routes/sources/[id]/components/Github.svelte b/apps/client/src/routes/sources/[id]/components/Github.svelte
new file mode 100644
index 000000000..b18b0a892
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/components/Github.svelte
@@ -0,0 +1,264 @@
+
+
+
+ {#if !source.githubAppId}
+
+ {:else if source.githubApp?.installationId}
+
+ {:else}
+
+ {/if}
+
diff --git a/apps/client/src/routes/sources/[id]/components/Gitlab.svelte b/apps/client/src/routes/sources/[id]/components/Gitlab.svelte
new file mode 100644
index 000000000..79f55c5c4
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/components/Gitlab.svelte
@@ -0,0 +1,333 @@
+
+
+
diff --git a/apps/client/src/routes/sources/[id]/components/New.svelte b/apps/client/src/routes/sources/[id]/components/New.svelte
new file mode 100644
index 000000000..113f6d0e3
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/components/New.svelte
@@ -0,0 +1,62 @@
+
+
+
+
+
+
Select a git type
+
+
+
+
+
+ {#if source?.type}
+
+ {#if source.type === 'github'}
+
+ {:else if source.type === 'gitlab'}
+
+ {/if}
+
+ {/if}
+
diff --git a/apps/client/src/routes/sources/[id]/components/Source.svelte b/apps/client/src/routes/sources/[id]/components/Source.svelte
new file mode 100644
index 000000000..4097720c5
--- /dev/null
+++ b/apps/client/src/routes/sources/[id]/components/Source.svelte
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Configuration
+
+
{source.name}
+
+ {#if source?.type === 'gitlab'}
+
+ {:else if source?.type === 'github'}
+
+ {/if}
+
+
+ {#if source.type === 'github'}
+
+ {:else if source.type === 'gitlab'}
+
+ {/if}
+
diff --git a/apps/server/src/trpc/index.ts b/apps/server/src/trpc/index.ts
index a5bc3e969..d3944f9f5 100644
--- a/apps/server/src/trpc/index.ts
+++ b/apps/server/src/trpc/index.ts
@@ -7,7 +7,8 @@ import {
dashboardRouter,
applicationsRouter,
servicesRouter,
- databasesRouter
+ databasesRouter,
+ sourcesRouter
} from './routers';
export const appRouter = router({
@@ -16,7 +17,8 @@ export const appRouter = router({
dashboard: dashboardRouter,
applications: applicationsRouter,
services: servicesRouter,
- databases: databasesRouter
+ databases: databasesRouter,
+ sources: sourcesRouter
});
export type AppRouter = typeof appRouter;
diff --git a/apps/server/src/trpc/routers/index.ts b/apps/server/src/trpc/routers/index.ts
index 0e16e9c41..b21ff2dca 100644
--- a/apps/server/src/trpc/routers/index.ts
+++ b/apps/server/src/trpc/routers/index.ts
@@ -4,3 +4,4 @@ export * from './settings';
export * from './applications';
export * from './services';
export * from './databases';
+export * from './sources';
diff --git a/apps/server/src/trpc/routers/sources/index.ts b/apps/server/src/trpc/routers/sources/index.ts
new file mode 100644
index 000000000..05ff96b51
--- /dev/null
+++ b/apps/server/src/trpc/routers/sources/index.ts
@@ -0,0 +1,223 @@
+import { z } from 'zod';
+import { privateProcedure, router } from '../../trpc';
+import { decrypt, encrypt } from '../../../lib/common';
+import { prisma } from '../../../prisma';
+
+import cuid from 'cuid';
+
+export const sourcesRouter = router({
+ save: privateProcedure
+ .input(
+ z.object({
+ id: z.string(),
+ name: z.string(),
+ htmlUrl: z.string(),
+ apiUrl: z.string(),
+ customPort: z.number(),
+ customUser: z.string(),
+ isSystemWide: z.boolean().default(false)
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ let { id, name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = input;
+ if (customPort) customPort = Number(customPort);
+ await prisma.gitSource.update({
+ where: { id },
+ data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
+ });
+ }),
+ newGitHubApp: privateProcedure
+ .input(
+ z.object({
+ id: z.string(),
+ name: z.string(),
+ htmlUrl: z.string(),
+ apiUrl: z.string(),
+ organization: z.string(),
+ customPort: z.number(),
+ isSystemWide: z.boolean().default(false)
+ })
+ )
+ .mutation(async ({ ctx, input }) => {
+ const { teamId } = ctx.user;
+ let { id, name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = input;
+
+ if (customPort) customPort = Number(customPort);
+ if (id === 'new') {
+ const newId = cuid();
+ await prisma.gitSource.create({
+ data: {
+ id: newId,
+ name,
+ htmlUrl,
+ apiUrl,
+ organization,
+ customPort,
+ isSystemWide,
+ type: 'github',
+ teams: { connect: { id: teamId } }
+ }
+ });
+ return {
+ id: newId
+ };
+ }
+ return null;
+ }),
+ newGitLabApp: privateProcedure
+ .input(
+ z.object({
+ id: z.string(),
+ type: z.string(),
+ name: z.string(),
+ htmlUrl: z.string(),
+ apiUrl: z.string(),
+ oauthId: z.number(),
+ appId: z.string(),
+ appSecret: z.string(),
+ groupName: z.string().optional().nullable(),
+ customPort: z.number().optional().nullable(),
+ customUser: z.string().optional().nullable()
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { teamId } = ctx.user;
+ let {
+ id,
+ type,
+ name,
+ htmlUrl,
+ apiUrl,
+ oauthId,
+ appId,
+ appSecret,
+ groupName,
+ customPort,
+ customUser
+ } = input;
+
+ if (oauthId) oauthId = Number(oauthId);
+ if (customPort) customPort = Number(customPort);
+ const encryptedAppSecret = encrypt(appSecret);
+
+ if (id === 'new') {
+ const newId = cuid();
+ await prisma.gitSource.create({
+ data: {
+ id: newId,
+ type,
+ apiUrl,
+ htmlUrl,
+ name,
+ customPort,
+ customUser,
+ teams: { connect: { id: teamId } }
+ }
+ });
+ await prisma.gitlabApp.create({
+ data: {
+ teams: { connect: { id: teamId } },
+ appId,
+ oauthId,
+ groupName,
+ appSecret: encryptedAppSecret,
+ gitSource: { connect: { id: newId } }
+ }
+ });
+ return {
+ status: 201,
+ id: newId
+ };
+ } else {
+ await prisma.gitSource.update({
+ where: { id },
+ data: { type, apiUrl, htmlUrl, name, customPort, customUser }
+ });
+ await prisma.gitlabApp.update({
+ where: { id },
+ data: {
+ appId,
+ oauthId,
+ groupName,
+ appSecret: encryptedAppSecret
+ }
+ });
+ }
+ }),
+ delete: privateProcedure
+ .input(
+ z.object({
+ id: z.string()
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { id } = input;
+ const source = await prisma.gitSource.delete({
+ where: { id },
+ include: { githubApp: true, gitlabApp: true }
+ });
+ if (source.githubAppId) {
+ await prisma.githubApp.delete({ where: { id: source.githubAppId } });
+ }
+ if (source.gitlabAppId) {
+ await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
+ }
+ }),
+ getSourceById: privateProcedure
+ .input(
+ z.object({
+ id: z.string()
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const { id } = input;
+ const { teamId } = ctx.user;
+ const settings = await prisma.setting.findFirst({});
+
+ if (id === 'new') {
+ return {
+ source: {
+ name: null,
+ type: null,
+ htmlUrl: null,
+ apiUrl: null,
+ organization: null,
+ customPort: 22,
+ customUser: 'git'
+ },
+ settings
+ };
+ }
+
+ const source = await prisma.gitSource.findFirst({
+ where: {
+ id,
+ OR: [
+ { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
+ { isSystemWide: true }
+ ]
+ },
+ include: { githubApp: true, gitlabApp: true }
+ });
+ if (!source) {
+ throw { status: 404, message: 'Source not found.' };
+ }
+
+ if (source?.githubApp?.clientSecret)
+ source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
+ if (source?.githubApp?.webhookSecret)
+ source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
+ if (source?.githubApp?.privateKey)
+ source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
+ if (source?.gitlabApp?.appSecret)
+ source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
+
+ return {
+ success: true,
+ data: {
+ source,
+ settings
+ }
+ };
+ })
+});