diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..f7572b6f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Deploy to AWS + +on: + push: + branches: [ main ] + +jobs: + aws-deployment: + name: Deploy + environment: + name: auth-server + url: https://github.com + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-2 + - name: Create CodeDeploy Deployment + id: deploy + run: | + aws deploy create-deployment --ignore-application-stop-failures \ + --application-name auth-server \ + --deployment-group-name auth-server \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --file-exists-behavior OVERWRITE \ + --github-location repository=${{ github.repository }},commitId=${{ github.sha }} diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 00000000..27d33b8a --- /dev/null +++ b/appspec.yml @@ -0,0 +1,14 @@ +version: 0.0 +os: linux +files: + - source: ./gitlangRoutes + destination: /home/ec2-user/auth-server +hooks: + ApplicationStop: + - location: stop.sh + timeout: 300 + runas: root + ApplicationStart: + - location: init.sh + timeout: 300 + runas: root diff --git a/client/app.ts b/client/app.ts index e75c5501..fa557045 100644 --- a/client/app.ts +++ b/client/app.ts @@ -1,8 +1,11 @@ import { createReadStream } from 'node:fs'; +import * as dotenv from 'dotenv'; import Koa from 'koa'; import serve from 'koa-static'; +dotenv.config(); + const app = new Koa(); const port = 3000; const redirect = 'docs'; diff --git a/client/routes/index.ts b/client/routes/index.ts index 0040a7b2..04b40905 100644 --- a/client/routes/index.ts +++ b/client/routes/index.ts @@ -1,18 +1,18 @@ import getSize from './helpers/size'; import languages from './requests/languages'; -import repos from './requests/repos'; +import repositories from './requests/repositories'; -const langs = async (inputOwner: string) => { +const langs = async (username: string) => { try { - window.history.pushState('', '', `/${inputOwner}`); - let owner = inputOwner; + window.history.pushState('', '', `/${username}`); + let owner = username; let allNames: string[]; - if (inputOwner.includes('/')) { - const [splitOwner, splitRepo] = inputOwner.split('/'); + if (username.includes('/')) { + const [splitOwner, splitRepo] = username.split('/'); owner = splitOwner; allNames = [splitRepo]; } else { - allNames = await repos(inputOwner); + allNames = await repositories(username); } const allLanguages = await languages(owner, allNames); const space = getSize(allLanguages.flat()); diff --git a/client/routes/requests/languages.ts b/client/routes/requests/languages.ts index be1c96e3..9cf28c2e 100644 --- a/client/routes/requests/languages.ts +++ b/client/routes/requests/languages.ts @@ -1,9 +1,29 @@ import axios from 'axios'; -const languages = async (owner: string, repos: string[]) => { +import router from '../../../local'; + +const localToken = process.env.GH_PAT; + +const localApi = async (owner: string, repos: string[]) => { + try { + const allLanguages = JSON.parse( + await router.langs(owner, repos, localToken), + ); + + if (allLanguages && allLanguages.length > 0) { + return allLanguages; + } + return []; + } catch (error) { + console.error('Error getting token from auth api', error); + return []; + } +}; + +const serverApi = async (owner: string, repos: string[]) => { try { const allLanguages: { data: { [key: string]: number }[] } = await axios.get( - 'https://api.5105015032.com/auth/gitlang/langs', + 'https://api.5105015032.com/gitlang/github/langs', { params: { owner, @@ -20,4 +40,6 @@ const languages = async (owner: string, repos: string[]) => { } }; +const languages = localToken ? localApi : serverApi; + export default languages; diff --git a/client/routes/requests/repos.ts b/client/routes/requests/repos.ts deleted file mode 100644 index 6857e2d1..00000000 --- a/client/routes/requests/repos.ts +++ /dev/null @@ -1,21 +0,0 @@ -import axios from 'axios'; - -const repos = async (username: string) => { - try { - const allRepos: { data: [] } = await axios.get( - 'https://api.5105015032.com/auth/gitlang/repos', - { - params: { username }, - }, - ); - if (allRepos && allRepos.data && allRepos.data.length > 0) { - return allRepos.data; - } - return []; - } catch (error) { - console.error('Error getting token from auth api', error); - return []; - } -}; - -export default repos; diff --git a/client/routes/requests/repositories.ts b/client/routes/requests/repositories.ts new file mode 100644 index 00000000..af2ef02b --- /dev/null +++ b/client/routes/requests/repositories.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; + +import router from '../../../local'; + +const localToken = process.env.GH_PAT; + +const localApi = async (username: string) => { + try { + const allRepos = JSON.parse(await router.repos(username, localToken)); + if (allRepos && allRepos.length > 0) { + return allRepos; + } + return []; + } catch (error) { + console.error('Error getting token from auth api', error); + return []; + } +}; + +const serverApi = async (username: string) => { + try { + const allRepos: { data: [] } = await axios.get( + 'https://api.5105015032.com/gitlang/github/repos', + { + params: { username }, + }, + ); + if (allRepos && allRepos.data && allRepos.data.length > 0) { + return allRepos.data; + } + return []; + } catch (error) { + console.error('Error getting token from auth api', error); + return []; + } +}; + +const repositories = localToken ? localApi : serverApi; + +export default repositories; diff --git a/gitlangRoutes/gitlang.ts b/gitlangRoutes/gitlang.ts new file mode 100644 index 00000000..8bdd1ef4 --- /dev/null +++ b/gitlangRoutes/gitlang.ts @@ -0,0 +1,9 @@ +import Router from '@koa/router'; + +import gitlangRouter from './requests/gitlangRouter'; + +const router = new Router({ prefix: '/gitlang' }); + +router.use(gitlangRouter.routes(), gitlangRouter.allowedMethods()); + +export default router; diff --git a/gitlangRoutes/helpers/github/auth.ts b/gitlangRoutes/helpers/github/auth.ts new file mode 100644 index 00000000..4ddfbc44 --- /dev/null +++ b/gitlangRoutes/helpers/github/auth.ts @@ -0,0 +1,36 @@ +import { + SecretsManagerClient, + GetSecretValueCommand, +} from '@aws-sdk/client-secrets-manager'; + +const region = 'us-east-2'; +const secretName = 'gitlang-auth'; + +const auth: () => Promise = async () => { + try { + let secret = ''; + + const client = new SecretsManagerClient({ + region, + }); + + const data = await client.send( + new GetSecretValueCommand({ SecretId: secretName }), + ); + + if (data && data.SecretString) { + secret = data.SecretString; + } + + const parser: { parse: (argument: string) => { [secretName]: string } } = + JSON; + + const parseSecret = parser.parse(secret); + return parseSecret[secretName]; + } catch (error) { + console.error(error); + return ''; + } +}; + +export default auth; diff --git a/gitlangRoutes/helpers/github/languages.ts b/gitlangRoutes/helpers/github/languages.ts new file mode 100644 index 00000000..4e5b9d13 --- /dev/null +++ b/gitlangRoutes/helpers/github/languages.ts @@ -0,0 +1,31 @@ +import { Octokit } from '@octokit/rest'; + +let octokit: Octokit; + +const fetchLanguage = async (owner: string, repo: string, token?: string) => { + try { + if (!octokit) { + if (!token) { + console.error('No token'); + return []; + } + octokit = new Octokit({ auth: token }); + } + return await octokit.paginate(octokit.rest.repos.listLanguages, { + owner, + repo, + }); + } catch { + return []; + } +}; + +const languages = async (owner: string, names: string[], token?: string) => { + const langs = []; + for (const repo of names) { + langs.push(await fetchLanguage(owner, repo, token)); + } + return langs; +}; + +export default languages; diff --git a/gitlangRoutes/helpers/github/repositories.ts b/gitlangRoutes/helpers/github/repositories.ts new file mode 100644 index 00000000..6f7c65f1 --- /dev/null +++ b/gitlangRoutes/helpers/github/repositories.ts @@ -0,0 +1,30 @@ +import { Octokit } from '@octokit/rest'; + +let octokit: Octokit; + +const repositories = async (username: string, token?: string) => { + try { + if (!octokit) { + if (!token) { + console.error('No token'); + return []; + } + octokit = new Octokit({ auth: token }); + } + return await octokit.paginate( + octokit.rest.repos.listForUser, + { + username, + type: 'owner', + }, + (response) => + response.data + .filter((repo: { fork: boolean }) => repo.fork === false) + .map((repo: { name: string }) => repo.name), + ); + } catch { + return []; + } +}; + +export default repositories; diff --git a/gitlangRoutes/requests/gitlangLocal.ts b/gitlangRoutes/requests/gitlangLocal.ts new file mode 100644 index 00000000..b6f32d45 --- /dev/null +++ b/gitlangRoutes/requests/gitlangLocal.ts @@ -0,0 +1,30 @@ +import languages from '../helpers/github/languages'; +import repositories from '../helpers/github/repositories'; + +const gitlangLocal = { + langs: async (owner: string, repos: string[], token?: string) => { + try { + const response = await languages(owner, repos, token); + if (response && response.length > 0) { + return JSON.stringify(response); + } + return JSON.stringify([]); + } catch { + return JSON.stringify([]); + } + }, + + repos: async (username: string, token?: string) => { + try { + const response = await repositories(username, token); + if (response && response.length > 0) { + return JSON.stringify(response); + } + return JSON.stringify([]); + } catch { + return JSON.stringify([]); + } + }, +}; + +export default gitlangLocal; diff --git a/gitlangRoutes/requests/gitlangRouter.ts b/gitlangRoutes/requests/gitlangRouter.ts new file mode 100644 index 00000000..05289abb --- /dev/null +++ b/gitlangRoutes/requests/gitlangRouter.ts @@ -0,0 +1,59 @@ +import Router from '@koa/router'; + +import auth from '../helpers/github/auth'; +import languages from '../helpers/github/languages'; +import repositories from '../helpers/github/repositories'; + +const router = new Router({ prefix: '/github' }); + +router.get( + '/langs', + async (context: { + request: { query: { owner: string; repos: string } }; + response: { status: number; body: string }; + }) => { + try { + const token = await auth(); + const { owner } = context.request.query; + const { repos } = context.request.query; + const repoList = JSON.parse(repos) as string[]; + const response = await languages(owner, repoList, token); + if (response && response.length > 0) { + context.response.status = 200; + context.response.body = JSON.stringify(response); + } else { + context.response.status = 404; + context.response.body = JSON.stringify([]); + } + } catch { + context.response.status = 404; + context.response.body = JSON.stringify([]); + } + }, +); + +router.get( + '/repos', + async (context: { + request: { query: { username: string } }; + response: { status: number; body: string }; + }) => { + try { + const token = await auth(); + const { username } = context.request.query; + const response = await repositories(username, token); + if (response && response.length > 0) { + context.response.status = 200; + context.response.body = JSON.stringify(response); + } else { + context.response.status = 404; + context.response.body = JSON.stringify([]); + } + } catch { + context.response.status = 404; + context.response.body = JSON.stringify([]); + } + }, +); + +export default router; diff --git a/init.sh b/init.sh new file mode 100644 index 00000000..65be39a8 --- /dev/null +++ b/init.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +yum update -y && yum upgrade -y +cd /home/ec2-user/auth-server +npm ci +screen -Sdm node npm start diff --git a/local.ts b/local.ts new file mode 100644 index 00000000..27e24ec4 --- /dev/null +++ b/local.ts @@ -0,0 +1,5 @@ +import gitlangLocal from './routes/requests/gitlangLocal'; + +const router = gitlangLocal; + +export default router; diff --git a/stop.sh b/stop.sh new file mode 100644 index 00000000..f572bea5 --- /dev/null +++ b/stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +killall screen