Skip to content
Draft
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
38 changes: 34 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ name: CI & CD
on:
push:
branches:
- '*'
- '**'
jobs:
Build-and-Deploy:
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }}

- name: Deploy to Vercel
id: vercel-deployment
uses: amondnet/vercel-action@v25
if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }}
with:
Expand All @@ -25,4 +24,35 @@ jobs:
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./
vercel-args: ${{ github.ref == 'refs/heads/main' && ' --prod' || '' }}
vercel-args: ${{ github.ref == 'refs/heads/main' && ' --prod' || ''}}

- name: Lark notification
uses: foxundermoon/feishu-action@v2
with:
url: ${{ secrets.LARK_CHATBOT_HOOK_URL }}
msg_type: post
content: |
post:
zh_cn:
title: Vercel 预览环境
content:
- - tag: text
text: Git 仓库:
- tag: a
text: ${{ github.server_url }}/${{ github.repository }}
href: ${{ github.server_url }}/${{ github.repository }}
- - tag: text
text: 代码分支:
- tag: a
text: ${{ github.ref }}
href: ${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }}
- - tag: text
text: 提交作者:
- tag: a
text: ${{ github.actor }}
href: ${{ github.server_url }}/${{ github.actor }}
- - tag: text
text: 预览链接:
- tag: a
text: ${{ steps.vercel-deployment.outputs.preview-url }}
href: ${{ steps.vercel-deployment.outputs.preview-url }}
4 changes: 4 additions & 0 deletions components/SocialIconBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
faBilibili,
faFreeCodeCamp,
faGithub,
faLinkedin,
faTwitter,
faWeibo,
faWeixin,
Expand All @@ -22,6 +24,8 @@ const IconMap = {
youtube: { icon: faYoutube, color: 'danger' },
bilibili: { icon: faBilibili, color: 'primary' },
podcast: { icon: faPodcast, color: 'warning' },
fcc: { icon: faFreeCodeCamp, color: 'success' },
linkedin: { icon: faLinkedin, color: 'primary' },
};

export type SocialIconBarProps = Pick<FontAwesomeIconProps, 'size'> &
Expand Down
23 changes: 23 additions & 0 deletions models/Base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HTTPClient } from 'koajax';
import { githubClient } from 'mobx-github';

export const isServer = () => typeof window === 'undefined';

Expand All @@ -14,3 +15,25 @@ export const ownClient = new HTTPClient({
baseURI: `${API_Host}/api/`,
responseType: 'json',
});

const GithubToken = process.env.GITHUB_TOKEN;

if (!isServer()) githubClient.baseURI = `${API_Host}/api/GitHub/`;

githubClient.use(({ request }, next) => {
if (GithubToken)
request.headers = {
authorization: `Bearer ${GithubToken}`,
...request.headers,
};

return next();
});

export { githubClient };

export interface Base {
id: number;
createdAt: string;
updatedAt: string;
}
18 changes: 1 addition & 17 deletions models/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
import {
githubClient,
GitRepository,
RepositoryFilter,
RepositoryModel as GitRepoModel,
} from 'mobx-github';
import { Stream } from 'mobx-restful';

import { API_Host, isServer } from './Base';

const GithubToken = process.env.GITHUB_TOKEN;

if (!isServer()) githubClient.baseURI = `${API_Host}/api/GitHub/`;

githubClient.use(({ request }, next) => {
if (GithubToken)
request.headers = {
authorization: `Bearer ${GithubToken}`,
...request.headers,
};
return next();
});

const MainOrganization = 'freeCodeCamp-China';

export class RepositoryModel extends Stream<GitRepository, RepositoryFilter>(
Expand Down Expand Up @@ -50,7 +34,7 @@ export class RepositoryModel extends Stream<GitRepository, RepositoryFilter>(
}

if (hasAdditional)
// @ts-ignore
// @ts-expect-error Mixin type issue
yield await this.getOne('freeCodeCamp/chinese', filter.relation);
}
}
Expand Down
21 changes: 21 additions & 0 deletions models/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { HTTPClient } from 'koajax';
import { SupabaseListModel } from 'mobx-supabase';

import { Base } from './Base';

export enum Gender {
Female = 0,
Male = 1,
Other = 2,
}

export interface User extends Base {
email: string;
gender?: Gender;
birthday?: string;
}

export class UserModel extends SupabaseListModel<User> {
baseURI = 'user';
client = new HTTPClient();
}
20 changes: 19 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NextConfig } from 'next';
import setPWA from 'next-pwa';
// @ts-expect-error No types available
import withLess from 'next-with-less';
Expand All @@ -10,4 +11,21 @@ const { NODE_ENV, CI } = process.env,
disable: NODE_ENV === 'development',
});

export default withLess(withPWA({ output: CI ? 'standalone' : undefined }));
const rewrites: NextConfig['rewrites'] = async () => ({
beforeFiles: [
{
source: '/proxy/github.com/:path*',
destination: 'https://github.com/:path*',
},
{
source: '/proxy/raw.githubusercontent.com/:path*',
destination: 'https://raw.githubusercontent.com/:path*',
},
],
afterFiles: [],
fallback: [],
});

export default withLess(
withPWA({ output: CI ? 'standalone' : undefined, rewrites }),
);
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@fortawesome/react-fontawesome": "^0.2.6",
"@giscus/react": "^3.1.0",
"idea-react": "^2.0.0-rc.13",
"koa": "^3.1.1",
"koajax": "^3.1.2",
"less": "^4.4.2",
"less-loader": "^12.3.0",
Expand All @@ -22,8 +23,10 @@
"mobx-github": "^0.6.2",
"mobx-i18n": "^0.7.2",
"mobx-react": "^9.2.1",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.4",
"mobx-restful-table": "^2.5.5",
"mobx-supabase": "^0.1.0",
"next": "^16.0.1",
"next-pwa": "~5.6.0",
"next-ssr-middleware": "^1.0.3",
Expand All @@ -44,6 +47,7 @@
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^5.5.0",
"@types/eslint-config-prettier": "^6.11.3",
"@types/koa": "^3.0.1",
"@types/lodash": "^4.17.20",
"@types/next-pwa": "^5.6.9",
"@types/node": "^22.19.0",
Expand All @@ -52,6 +56,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"git-utility": "^0.2.1",
"globals": "^16.5.0",
"husky": "^9.1.7",
"jiti": "^2.6.1",
Expand Down Expand Up @@ -86,6 +91,7 @@
"scripts": {
"prepare": "husky || true",
"install": "next typegen",
"postinstall": "xgit download https://github.com/freeCodeCamp-China/key-vault main Web-site || true",
"dev": "next dev --webpack",
"build": "next build --webpack",
"start": "next start",
Expand Down
13 changes: 11 additions & 2 deletions pages/api/GitHub/[...slug].ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { proxyGithub } from './core';
import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware';

export default proxyGithub();
import { safeAPI } from '../core';
import { proxyGitHubAll } from './core';

export const config = { api: { bodyParser: false } };

const router = createKoaRouter(import.meta.url);

router.get('/(.*)', safeAPI, proxyGitHubAll);

export default withKoaRouter(router);
48 changes: 31 additions & 17 deletions pages/api/GitHub/core.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import { githubClient } from 'mobx-github';
import { Context, Middleware } from 'koa';
import { githubOAuth2 } from 'next-ssr-middleware';

import { safeAPI } from '../core';
import { githubClient } from '../../../models/Base';

export const proxyGithub = <T>(dataFilter?: (path: string, data: T) => T) =>
safeAPI(async ({ method, url, headers, body }, response) => {
delete headers.host;
export const proxyGithub = async <T>({
method,
url,
headers: { host, ...headers },
request,
}: Context) => {
const path = url!.slice(`/api/GitHub/`.length),
body = Reflect.get(request, 'body');

const path = url!.slice(`/api/GitHub/`.length);
// @ts-expect-error KoAJAX type compatibility
return githubClient.request<T>({ method, path, headers, body });
};

const { status, body: data } = await githubClient.request<T>({
// @ts-expect-error Upstream Type compatibility issue
method,
path,
// @ts-expect-error Upstream Type compatibility issue
headers,
body: body || undefined,
});
export const proxyGitHubAll: Middleware = async context => {
const { status, body } = await proxyGithub(context);

response.status(status);
response.send(dataFilter?.(path, data as T) || data);
});
context.status = status;
context.body = body;
};

const client_id = process.env.GITHUB_OAUTH_CLIENT_ID!,
client_secret = process.env.GITHUB_OAUTH_CLIENT_SECRET!;

export const ProxyBaseURL = 'https://chinese.fcc-cd.dev/proxy';

export const githubSigner = githubOAuth2({
rootBaseURL: process.env.VERCEL ? undefined : `${ProxyBaseURL}/github.com/`,
client_id,
client_secret,
scopes: ['user:email', 'read:user', 'public_repo', 'read:project'],
});
66 changes: 30 additions & 36 deletions pages/api/core.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,38 @@
import { Context, Middleware } from 'koa';
import { HTTPError } from 'koajax';
import { NextApiRequest, NextApiResponse } from 'next';
import { ProxyAgent, setGlobalDispatcher } from 'undici';

const { HTTP_PROXY } = process.env;

if (HTTP_PROXY) setGlobalDispatcher(new ProxyAgent(HTTP_PROXY));

export type NextAPI = (
req: NextApiRequest,
res: NextApiResponse,
) => Promise<any>;

export function safeAPI(handler: NextAPI): NextAPI {
return async (req, res) => {
try {
return await handler(req, res);
} catch (error) {
if (!(error instanceof HTTPError)) {
console.error(error);

res.status(400);
return res.send({ message: (error as Error).message });
}
const { message, response } = error;
let { body } = response;

res.status(response.status);
res.statusMessage = message;

if (body instanceof ArrayBuffer)
try {
body = new TextDecoder().decode(new Uint8Array(body));
console.error(body);

body = JSON.parse(body);
console.error(body);
} catch {
//
}
res.send(body);
export const safeAPI: Middleware<any, any> = async (context: Context, next) => {
try {
return await next();
} catch (error) {
if (!(error instanceof HTTPError)) {
console.error(error);

context.status = 400;

return (context.body = { message: (error as Error).message });
}
};
}
const { message, response } = error;
let { body } = response;

context.status = response.status;
context.statusMessage = message;

if (body instanceof ArrayBuffer)
try {
body = new TextDecoder().decode(new Uint8Array(body));

body = JSON.parse(body);
} catch {
//
}
console.error(JSON.stringify(body, null, 2));

context.body = body;
}
};
Loading