Skip to content

Commit

Permalink
Merge branch 'main' of github.com:geprog/codecaptain
Browse files Browse the repository at this point in the history
  • Loading branch information
anbraten committed Apr 19, 2024
2 parents d921672 + afb2e9a commit abe9462
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 42 deletions.
5 changes: 4 additions & 1 deletion middleware/auth.global.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export default defineNuxtRouteMiddleware(async (to, from) => {
const { isAuthenticated } = await useAuth();
if (to.path.startsWith('/api')) {
return;
}

const { isAuthenticated } = await useAuth();
if (!isAuthenticated.value && to.path !== '/auth/login') {
return navigateTo('/auth/login');
}
Expand Down
27 changes: 24 additions & 3 deletions pages/repos/[repo_id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,27 @@
</div>
</Card>
</NuxtLink>

<Card clickable @click="newChat">
<div class="flex flex-col items-center justify-between p-2 gap-2 w-64">
<h2 class="text-xl font-bold">Add new chat</h2>
<UButton icon="i-heroicons-plus" class="mt-8" label="Create" />
</div>
</Card>
</div>
</div>
</div>
</template>

<script lang="ts" setup>
const { refresh: refreshRepos } = await useRepositoriesStore();
const { chats } = await useChatsStore();
const { chats, refresh: refreshChats } = await useChatsStore();
const route = useRoute();
const toast = useToast();
const repoId = computed(() => route.params.repo_id);
const { data: repo } = await useFetch(() => `/api/repos/${repoId.value}`);
const { data: repo, refresh: refreshRepo } = await useFetch(() => `/api/repos/${repoId.value}`);
const repoChats = computed(() => chats.value.filter((chat) => chat.repoId === repo.value?.id));
const indexing = ref(false);
Expand Down Expand Up @@ -115,6 +122,7 @@ async function reIndex() {
});
}
indexing.value = false;
await refreshRepo();
}
async function deleteRepo() {
Expand All @@ -137,9 +145,22 @@ async function deleteRepo() {
});
await refreshRepos();
await navigateTo('/');
}
async function newChat() {
if (!repo.value) {
throw new Error('Unexpected: Repo not loaded');
}
const chat = await $fetch('/api/chats', {
method: 'POST',
body: JSON.stringify({ repoId: repo.value.id }),
});
await navigateTo(`/chats/${chat.id}`);
await refreshChats();
}
</script>

<style scoped>
Expand Down
20 changes: 7 additions & 13 deletions pages/repos/add.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,23 @@
<div class="w-full rounded-md border border-zinc-400">
<div v-if="loading" class="p-4">Loading ...</div>

<div v-else-if="!selectedForge" class="p-4">Select a forge first.</div>

<div v-else-if="search.length < 1" class="p-4">Start typing to search for a repository</div>

<div v-else-if="!repositories || repositories.length === 0" class="p-4">No repository found</div>

<div
v-else
v-for="repo in repositories?.slice(0, 5) || []"
v-for="repo in repositories?.slice(0, 5).toSorted((r) => (r.internalId ? 1 : -1)) || []"
:key="repo.remoteId"
class="flex border-b border-zinc-200 items-center px-2 py-4 gap-2 w-full min-w-0"
>
<img v-if="repo.avatarUrl" :src="repo.avatarUrl" alt="icon" class="w-6 h-6 rounded-md" />
<UIcon v-else name="i-ion-git-branch" class="w-6 h-6" />
<span class="font-bold flex-wrap truncate overflow-ellipsis">{{ repo.name }}</span>
<div class="flex-grow" />
<UButton v-if="repo.internalId" :to="`/repos/${repo.internalId}/chat`" label="Open" />
<UButton v-if="repo.internalId" :to="`/repos/${repo.internalId}`" label="Open" />
<UButton v-else @click="addRepo(repo.remoteId)" label="Add" />
</div>
</div>
Expand All @@ -61,21 +65,11 @@ const { data: forges } = await useFetch<Forge[]>('/api/user/forges', {
});
const selectedForge = ref<Forge>();
watch(
forges,
() => {
if (!selectedForge.value && forges.value?.length) {
selectedForge.value = forges.value[0];
}
},
{ immediate: true },
);
const search = ref('');
const loading = ref(false);
const { data: repositories } = await useAsyncData(
async () => {
if (!selectedForge.value) {
if (!selectedForge.value || search.value.length < 1) {
return Promise.resolve([]);
}
Expand Down
50 changes: 38 additions & 12 deletions pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,31 @@
<h2 class="text-xl font-bold mt-4">Forges</h2>

<div class="flex flex-wrap gap-4 mt-2">
<NuxtLink v-for="forge in forges" :key="forge.id" :to="`/forges/${forge.id}`" :title="forge.host">
<Card clickable>
<div class="flex flex-col items-center justify-between p-2 h-full gap-2 w-64">
<div class="flex items-center gap-2 mt-2 mx-auto">
<UIcon v-if="forge.type === 'github'" name="i-ion-logo-github" class="w-6 h-6 flex-shrink-0" />
<UIcon v-if="forge.type === 'gitlab'" name="i-ion-logo-gitlab" class="w-6 h-6 flex-shrink-0" />
<span class="font-semibold text-xl truncate text-center">{{ forge.host }}</span>
</div>
<Card v-for="forge in forges" :key="forge.id">
<div class="flex flex-col items-center justify-between p-2 h-full gap-2 w-64">
<div class="flex items-center gap-2 mt-2 mx-auto">
<UIcon v-if="forge.type === 'github'" name="i-ion-logo-github" class="w-6 h-6 flex-shrink-0" />
<UIcon v-if="forge.type === 'gitlab'" name="i-ion-logo-gitlab" class="w-6 h-6 flex-shrink-0" />
<span class="font-semibold text-xl truncate text-center">{{ forge.host }}</span>
</div>

<div class="flex gap-2">
<UButton
v-if="forge.isOwner"
icon="i-heroicons-pencil-square"
class="mt-8"
label="Edit"
:to="`/forges/${forge.id}`"
/>

<UButton icon="i-heroicons-pencil-square" class="mt-8" label="Edit" :disabled="!forge.isOwner" />
<a
:href="`/api/auth/login?forgeId=${forge.id}&redirectUrl=${urlEncoded(`/settings?successful=true&forgeId=${forge.id}`)}`"
>
<UButton icon="i-ion-log-in-outline" class="mt-8" label="Login" />
</a>
</div>
</Card>
</NuxtLink>
</div>
</Card>

<NuxtLink to="/forges/add" class="flex">
<Card clickable>
Expand Down Expand Up @@ -61,7 +73,21 @@
</template>

<script setup lang="ts">
const route = useRoute();
const toast = useToast();
const { forges } = await useForgesStore();
const { repos } = await useRepositoriesStore();
const urlEncoded = encodeURIComponent;
onMounted(() => {
if (route.query.successful) {
const forge = forges.value.find((forge) => forge.id.toString() === route.query.forgeId);
if (!forge) return;
toast.add({
title: `Successfully logged in to ${forge.host}`,
color: 'green',
});
}
});
</script>
34 changes: 24 additions & 10 deletions server/api/auth/callback.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,45 @@ import { User, forgeSchema, userForgesSchema, userSchema } from '~/server/schema
import { and, eq } from 'drizzle-orm';
import { getForgeFromDB } from '~/server/forges';

async function loginUser(event: H3Event, user: User) {
async function loginUser(event: H3Event, user: User, redirectUrl: string | undefined = undefined) {
const session = await useAuthSession(event);

await session.update({
userId: user.id,
});

return sendRedirect(event, '/');
return sendRedirect(event, redirectUrl ?? '/');
}

export default defineEventHandler(async (event) => {
const authenticatedUser = await getUser(event);

const { state } = getQuery(event);
if (!state) {
throw new Error('State is undefined');
throw createError({
status: 400,
message: 'Missing state query parameter',
});
}

const session = await useStorage().getItem<{ loginToForgeId: number }>(`oauth:${state}`);
const session = await useStorage().getItem<{ loginToForgeId: number; redirectUrl?: string }>(`oauth:${state}`);
if (!session) {
throw new Error('Session not found');
throw createError({
status: 400,
message: 'Session not found',
});
}

console.log('session', session);

const forgeId = session.loginToForgeId;

const forgeModel = await db.select().from(forgeSchema).where(eq(forgeSchema.id, forgeId)).get();
if (!forgeModel) {
throw new Error(`Forge with id ${forgeId} not found`);
throw createError({
status: 404,
message: `Forge with id ${forgeId} not found`,
});
}

const forge = getForgeFromDB(forgeModel);
Expand Down Expand Up @@ -73,11 +84,14 @@ export default defineEventHandler(async (event) => {
})
.run();

return loginUser(event, user);
return loginUser(event, user, session.redirectUrl);
}

if (!forgeModel.allowLogin) {
throw new Error('Login not allowed for this forge');
throw createError({
status: 400,
message: 'Login not allowed for this forge',
});
}

// try to find user by its remoteUserId
Expand Down Expand Up @@ -110,7 +124,7 @@ export default defineEventHandler(async (event) => {
.where(eq(userForgesSchema.id, userForge.id))
.run();

return loginUser(event, user);
return loginUser(event, user, session.redirectUrl);
}

// completely new user => create user and userForge and login
Expand All @@ -136,5 +150,5 @@ export default defineEventHandler(async (event) => {
})
.run();

return loginUser(event, user);
return loginUser(event, user, session.redirectUrl);
});
20 changes: 17 additions & 3 deletions server/api/auth/login.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { getForgeFromDB } from '~/server/forges';
import { randomBytes } from 'crypto';

export default defineEventHandler(async (event) => {
const forgeId = getQuery<{ forgeId: string }>(event).forgeId;
const { forgeId, redirectUrl } = getQuery<{ forgeId: string; redirectUrl?: string }>(event);
if (!forgeId) {
throw new Error('Forge id is undefined');
throw createError({
status: 400,
message: 'Missing forgeId query parameter',
});
}

const forgeModel = await db
Expand All @@ -16,15 +19,26 @@ export default defineEventHandler(async (event) => {
.get();

if (!forgeModel) {
throw new Error(`Forge with id ${forgeId} not found`);
throw createError({
status: 404,
message: `Forge with id ${forgeId} not found`,
});
}

const forge = getForgeFromDB(forgeModel);

const stateId = randomBytes(64).toString('hex');

if (redirectUrl && !redirectUrl.startsWith('/')) {
throw createError({
status: 400,
message: 'Invalid redirectUrl parameter',
});
}

await useStorage().setItem(`oauth:${stateId}`, {
loginToForgeId: forgeModel.id,
redirectUrl,
});

const config = useRuntimeConfig();
Expand Down

0 comments on commit abe9462

Please sign in to comment.