Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow user to use other forges #16

Merged
merged 91 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
308f74d
fix: duplicate insertion of userdata
aaa006bd Aug 8, 2023
60897c8
feat: check for existing users
aaa006bd Aug 8, 2023
2baef12
feat: add forge configs
anbraten Aug 8, 2023
98986f7
feat support gitlab login
anbraten Aug 8, 2023
3808ff5
fix: useFetch in UI
aaa006bd Aug 9, 2023
198291f
fix: updated env sample
aaa006bd Aug 9, 2023
0c42433
fix: proper data extraction from useFetch
aaa006bd Aug 9, 2023
4aeffeb
improve oauth flow
anbraten Aug 9, 2023
e2f338a
improve ui
anbraten Aug 9, 2023
2d736d6
chore: renamed schema files
aaa006bd Aug 9, 2023
0007851
chore: added tokens in schema
aaa006bd Aug 9, 2023
b658734
chore: types updated
aaa006bd Aug 9, 2023
184e7c3
chore: token extraction from github client
aaa006bd Aug 9, 2023
6b00f2d
chore: token extraction from gitlab client
aaa006bd Aug 9, 2023
1632ba3
chore: store tokens in database
aaa006bd Aug 9, 2023
1e507ba
fix: insert tokens
aaa006bd Aug 9, 2023
2c3aae4
chore: added tokens method for forges
aaa006bd Aug 9, 2023
462a51b
chore: added token setting during forge connection
aaa006bd Aug 9, 2023
77f391b
chore: added access token expiration to db
aaa006bd Aug 9, 2023
8cf6be2
chore: removed expire time from db
aaa006bd Aug 10, 2023
ea2dd55
chore: added method for token verification
aaa006bd Aug 10, 2023
3545ee3
chore: refactoring of forge classes
aaa006bd Aug 10, 2023
355c78b
fix: types for forges
aaa006bd Aug 10, 2023
9d3c767
chore: token validation in forges class
aaa006bd Aug 10, 2023
87d4526
chore: method name refactored
aaa006bd Aug 10, 2023
72ce03c
fix: correct tokens for user info
aaa006bd Aug 10, 2023
845bf3f
chore: token retrival in callback
aaa006bd Aug 10, 2023
6617cd5
fix: token assignment
aaa006bd Aug 10, 2023
3664deb
chore: get userinfo from url
aaa006bd Aug 10, 2023
de1efc7
cleanup code
anbraten Aug 10, 2023
d628320
improve user forge api
anbraten Aug 10, 2023
9206a45
improve ui
anbraten Aug 10, 2023
a88db0c
nits
anbraten Aug 10, 2023
e960c54
nits
anbraten Aug 10, 2023
2303ec8
fix: server side rendering crash
aaa006bd Aug 14, 2023
3a25608
fix: revert browser check
aaa006bd Aug 14, 2023
59b7cea
fix: refresh token payload for github
aaa006bd Aug 15, 2023
e12d434
fix render issue
anbraten Aug 15, 2023
30d6d18
chore: update refresh token in db
aaa006bd Aug 15, 2023
3bc5d1e
fix forges tokens refreshing
anbraten Aug 15, 2023
690c4fc
improve search and repo name
anbraten Aug 15, 2023
782478f
add repo schema, some todos
anbraten Aug 15, 2023
0464eef
adjust endpoints
anbraten Aug 16, 2023
39a1ab7
chore: tidy up migrations
aaa006bd Aug 16, 2023
012adf6
chore: check repo access for user and load repo from db
aaa006bd Aug 17, 2023
afd72b0
chore: get repo from db in clone
aaa006bd Aug 17, 2023
e29c88c
fix: api body
aaa006bd Aug 18, 2023
65df1d2
chore: added permission check while fetching repo
aaa006bd Aug 18, 2023
7eb42c6
chore: seperate indexing point
aaa006bd Aug 18, 2023
288e08c
chore: moved indexing from cloning
aaa006bd Aug 18, 2023
e8fa336
fix: empty user repo id array check
aaa006bd Aug 18, 2023
91a9719
chore: forgeId from github login
aaa006bd Aug 18, 2023
f1bf86f
fix: search api
aaa006bd Aug 18, 2023
4af21d3
fix: pass token expiration
aaa006bd Aug 18, 2023
119f54f
fix: token type
aaa006bd Aug 18, 2023
545784e
fix: adjust repo search endpoint
aaa006bd Aug 18, 2023
15f63e0
chore: added routing for connected forges
aaa006bd Aug 18, 2023
e3b9103
chore: adjusted cloning endpoint
aaa006bd Aug 18, 2023
fcfdf17
fix: return types
aaa006bd Aug 18, 2023
f12f78c
chore: unauthorized user error
aaa006bd Aug 18, 2023
8ea6768
chore: data mapping for repo
aaa006bd Aug 21, 2023
2915932
chore: get repo by id
aaa006bd Aug 21, 2023
748518c
chore: testing github api for fetching repos by id
aaa006bd Aug 21, 2023
88847ad
fix repo adding
anbraten Aug 21, 2023
e56851a
fix repo parsing
anbraten Aug 21, 2023
3a7eb91
fix ts
anbraten Aug 21, 2023
8b06b67
fix: insert user repo id
aaa006bd Aug 21, 2023
fb213bc
fix empty repo list
anbraten Aug 21, 2023
1448a95
Merge branch '11-create-a-work-flow-for-github-provider' of github.co…
anbraten Aug 21, 2023
18d3021
fix repo listing
anbraten Aug 21, 2023
ed0ab5c
fix conflicting repo adding
anbraten Aug 21, 2023
4b7df65
pass auth
anbraten Aug 21, 2023
ec5b4d5
chore: load active repos from db
aaa006bd Aug 22, 2023
1f490d2
chore: cleanup code
aaa006bd Aug 22, 2023
3eda0a7
chore: clone updated
aaa006bd Aug 22, 2023
b07cd47
chore: added future task for backend api
aaa006bd Aug 22, 2023
fc5c352
fix: format
aaa006bd Aug 22, 2023
2e6a389
some cleanup
anbraten Aug 24, 2023
9ed5fc0
Merge branch '11-create-a-work-flow-for-github-provider' of github.co…
anbraten Aug 24, 2023
b8c9efe
more cleanup
anbraten Aug 24, 2023
90a08d9
improve forges code and finish clone
anbraten Aug 25, 2023
09b8d6a
cleanup auth
anbraten Aug 25, 2023
7f38f59
cleanup
anbraten Aug 25, 2023
d67134e
proper error handling and cleanups
anbraten Aug 25, 2023
12cecd4
use session auth
anbraten Aug 25, 2023
9908251
fix ui
anbraten Aug 25, 2023
63c6b56
cleanup
anbraten Aug 25, 2023
0357d1d
fix ts
anbraten Aug 25, 2023
68add6d
fix drizzle config
anbraten Aug 25, 2023
8c9381f
fix adding repos
anbraten Aug 25, 2023
bce933d
use clone credentials
anbraten Aug 25, 2023
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: 2 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
OPENAI_API_KEY=
NUXT_PUBLIC_GITHUB_CLIENT_ID=
NUXT_GITHUB_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
NUXT_API_URL=
DATA_PATH=./data
DATABASE_NAME=
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pnpm-lock.yaml
dist
coverage/
.pnpm-store/
server/db
migrations/meta/*.json
migrations/*.sql
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ Use the command `python uvicorn pyserver:app --reload --app-dir=ai` to start the

## Database

We are using Drizzle ORM and sqlite as of now. The schemas need to be inside db/schemas and the migrations will be inside db/migrations. checkout `drizzle.config.ts` for the settings.
use `pnpm push-schema` to directly update the database with new table schemas. This is only intended for development only. Use other commands along with `await migrate(db, { migrationsFolder: "" });` function call in a production environment. More information [https://orm.drizzle.team/kit-docs/quick](here). User `pnpm db-exporer` to open drizzle studio which is in beta state right now.
Codecaptain is using [Drizzle ORM](https://orm.drizzle.team) and sqlite.

### Migrations

- To generate new migrations run: `pnpm db:generate-migration`
- To migrate the database and seed it run: `pnpm db:up`
- To open the database explorer run: `pnpm db:explorer`

## Appreciations

Expand Down
43 changes: 43 additions & 0 deletions composables/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import jwt_decode from 'jwt-decode';

export const useAuth = () => {
const token = useCookie('token');
const user = ref<{
name: string;
avatarUrl: string;
email: string;
}>();

function login(forgeId: number) {
//TODO: find a better way to share the forge id that is connected.
localStorage.setItem('forgeId', String(forgeId));
anbraten marked this conversation as resolved.
Show resolved Hide resolved
window.location.href = `/api/auth/login?forgeId=${forgeId}`;
}

function logout() {
localStorage.removeItem('forgeId');
anbraten marked this conversation as resolved.
Show resolved Hide resolved
window.location.href = '/api/auth/logout';
}

if (token.value) {
const decodedToken = jwt_decode(token.value) as { userId: string; exp: number; iat: number };

// TODO: logout if token is expired and we are in the browser => check if there is a better way
if (decodedToken.exp * 1000 < Date.now() && globalThis.window) {
logout();
}

// TODO: load user data
// TODO: fix for SSR
(async () => {
user.value = await $fetch('/api/user');
})();
}

return {
isAuthenticated: !!token.value,
user,
login,
logout,
};
};
33 changes: 0 additions & 33 deletions composables/github.ts

This file was deleted.

2 changes: 1 addition & 1 deletion drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Config } from 'drizzle-kit';
// For config references visit https://orm.drizzle.team/kit-docs/config-reference

export default {
schema: ['./server/**/*.schema.ts'],
schema: ['./server/schemas/*schemas.ts'],
out: './server/db/migrations',
dbCredentials: {
url: 'code_captain.db',
Expand Down
13 changes: 8 additions & 5 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
>
<div class="ml-auto flex gap-4 items-center">
<template v-if="user">
<img :src="user.avatar_url" alt="User profile icon" class="w-8 h-auto rounded-full" />
<img :src="user.avatarUrl" alt="User profile icon" class="w-8 h-auto rounded-full" />
<button
class="border rounded px-2 py-1 flex items-center justify-center gap-1 hover:bg-black hover:text-white"
@click="logout"
>
Logout
</button>
</template>
<Button v-else @click="login"> <Icon name="fa-brands:github" /> Login with GitHub </Button>
<template v-else>
<Button v-for="forge in forges" :key="forge.id" @click="login(forge.id)">
<Icon name="fa-brands:github" /> Login with {{ forge.name }}
</Button>
</template>
</div>
</header>

Expand All @@ -31,9 +35,8 @@
</template>

<script setup lang="ts">
const login = githubLogin;
const logout = githubLogout;
const user = await fetchGithubUser();
const { user, login, logout } = useAuth();
const forges = await $fetch('/api/forges');
</script>

<style>
Expand Down
8 changes: 0 additions & 8 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss', 'nuxt-icon'],
runtimeConfig: {
public: {
github: {
clientId: process.env.NUXT_PUBLIC_GITHUB_CLIENT_ID,
},
},
github: {
clientSecret: process.env.NUXT_GITHUB_CLIENT_SECRET,
},
api: {
url: process.env.NUXT_API_URL || 'http://localhost:8000',
},
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"db:drop": "drizzle-kit drop",
"db:push": "drizzle-kit push:sqlite",
"db:explorer": "drizzle-kit studio",
"db:up": "tsx server/contrib/index.ts",
"clean": "rm -rf dist/ node_modules/ .eslintcache",
"format:check": "prettier --check .",
"format:fix": "prettier --write .",
Expand All @@ -19,17 +20,24 @@
"devDependencies": {
"@nuxt/devtools": "latest",
"@nuxtjs/tailwindcss": "6.8.0",
"@types/node": "18",
"@types/better-sqlite3": "7.6.4",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "18",
"better-sqlite3": "8.5.0",
"dotenv": "^16.3.1",
"drizzle-kit": "^0.19.12",
"nuxt": "3.6.1",
"prettier": "^3.0.1",
"tsx": "^3.12.7",
"typescript": "^5.1.6"
},
"dependencies": {
"@gitbeaker/core": "^39.10.3",
"@gitbeaker/rest": "^39.10.3",
"better-sqlite3": "8.5.0",
"drizzle-orm": "0.27.2",
"jsonwebtoken": "^9.0.1",
"jwt-decode": "^3.1.2",
"nuxt-icon": "0.4.1",
"octokit": "2.1.0",
"simple-git": "3.19.1",
Expand Down
31 changes: 22 additions & 9 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

<div class="flex flex-wrap gap-4 justify-center mt-4 min-h-fit max-w-4xl">
<template v-if="repositories">
<Card v-for="repo in repositories.filter((r) => r.active)" :key="repo.id" :href="`/repos/${repo.id}/chat`">
<Card v-for="repo in repositories" :key="repo.id" :href="`/repos/${repo.id}/chat`">
<div class="flex flex-col items-center p-2 gap-2 w-64 h-full justify-between">
<span class="font-bold text-gray-300 text-xl">{{ repo.full_name }}</span>
<span class="font-bold text-gray-300 text-xl">{{ repo.name }}</span>
<Button class="flex justify-center items-center">Open</Button>
</div>
</Card>

<Card href="/repos/add">
<div class="flex flex-col items-center justify-between p-2 h-full gap-2 w-64">
<span class="font-bold text-gray-300 text-xl">Add a repository</span>
Expand All @@ -19,17 +18,31 @@
</Card>
</template>
</div>

<div v-if="forges && forges.length > 0" class="flex flex-col mt-8">
<span class="text-xl font-bold">Forges</span>
<div v-for="forge in forges" :key="forge.id">
<Button v-if="!forge.isConnected" class="flex justify-center items-center" @click="login(forge.id)"
>Connect to {{ forge.name }}</Button
>
<NuxtLink v-else to="/repos/add">{{ forge.name }} {{ forge.host }} (connected)</NuxtLink>
</div>
</div>
</div>
</template>

<script setup lang="ts">
const githubCookie = useGithubCookie();
const { login } = useAuth();

const repositories = ref(
await $fetch('/api/repos/list', {
headers: {
gh_token: githubCookie.value!,
},
}),
(
await useFetch('/api/repos/list', {
server: false,
})
).data,
);

const { data: forges } = await useFetch('/api/user/forges', {
server: false,
});
</script>
54 changes: 38 additions & 16 deletions pages/repos/add.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@
<span class="m-auto text-2xl">Cloning and indexing repo ...</span>
</div>

<div v-else-if="!selectedForgeId">
<li
v-for="forge in forges?.filter((f) => f.isConnected)"
:key="forge.id"
class="cursor-pointer hover:underline"
@click="selectedForgeId = forge.id"
>
{{ forge.name }} - {{ forge.host }}
</li>
</div>

<div v-else class="mx-auto flex flex-col items-center max-w-2xl">
<div v-if="selectedForge" class="flex items-center gap-2">
<span class="text-xl font-bold">{{ selectedForge?.name }} - {{ selectedForge.host }}</span>
<div @click="selectedForgeId = undefined" class="cursor-pointer">x</div>
</div>

<TextInput
:model-value="search"
placeholder="Search for a repo ..."
Expand All @@ -18,34 +34,35 @@
:key="repo.id"
class="flex border-b border-gray-200 items-center p-2 gap-2 w-full justify-between min-w-0"
>
<span class="font-bold text-gray-300 flex-wrap truncate overflow-ellipsis">{{ repo.full_name }}</span>
<span class="font-bold text-gray-300 flex-wrap truncate overflow-ellipsis">{{ repo.name }}</span>
<Button v-if="repo.active" :href="`/repos/${repo.id}/chat`">Open</Button>
<Button v-else @click="cloneRepo(repo.id)">Activate</Button>
<Button v-else @click="cloneRepo(repo.id)">Import</Button>
</div>
</div>
</div>
</template>

<script setup lang="ts">
const router = useRouter();
const githubCookie = useGithubCookie();
const user = await fetchGithubUser();
const loading = ref(false);
const { user } = useAuth();
const { data: forges } = await useFetch('/api/user/forges', {
server: false,
});
const selectedForgeId = ref<number>();
const selectedForge = computed(() => forges.value?.find((f) => f.id === selectedForgeId.value));

const search = ref(`user:${user.value?.login}`);
const search = ref('');
const { data: repositories } = await useAsyncData(
'repositories',
() =>
$fetch('/api/repos/search', {
headers: {
gh_token: githubCookie.value!,
},
$fetch(`/api/forges/${selectedForgeId.value}/repos/search`, {
query: {
search: search.value,
},
}),
{
watch: [search],
server: false,
watch: [search, selectedForgeId],
},
);

Expand All @@ -60,18 +77,23 @@ function debounce<T extends Function>(cb: T, wait = 20) {

const updateSearch = debounce((_search: string) => {
search.value = _search;
}, 500);
console.log('search', _search);
}, 1000);

async function cloneRepo(repoId: string) {
loading.value = true;
const forgeId = selectedForgeId.value;
try {
await $fetch(`/api/repos/${repoId}/clone`, {
key: `cloneRepo-${repoId}`,
await $fetch(`/api/forges/${forgeId}/repos/add`, {
method: 'POST',
headers: {
gh_token: githubCookie.value!,
body: {
repoId,
},
});
} catch (error) {
console.log(error);
}
try {
await navigateTo(`/repos/${repoId}/chat`);
} catch (error) {
console.error(error);
Expand Down
Loading
Loading