Skip to content

Commit

Permalink
[add] IdeaMall & EasyWebApp OSS based on GitHub API proxy (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
TechQuery authored Jan 30, 2024
1 parent 5a56924 commit 34a68d5
Show file tree
Hide file tree
Showing 20 changed files with 1,149 additions and 892 deletions.
4 changes: 0 additions & 4 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
#!/bin/sh

. "$(dirname "$0")/_/husky.sh"

npm test
4 changes: 0 additions & 4 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
#!/bin/sh

. "$(dirname "$0")/_/husky.sh"

npm run build
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

FROM node:18-slim AS base
RUN apt-get update && \
apt-get install ca-certificates curl -y --no-install-recommends
apt-get install ca-certificates curl libjemalloc-dev -y --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
# set environment variable to preload JEMalloc
ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
# set GC time, set arenas number, set background_thread run GC
ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
Expand Down
24 changes: 24 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
presets: [
// https://babeljs.io/docs/babel-preset-typescript
[
'@babel/preset-typescript',
{
allowDeclareFields: true,
allowNamespaces: true,
allExtensions: true,
isTSX: true,
},
],
// https://babeljs.io/docs/babel-preset-react
[
'@babel/preset-react',
{
runtime: 'automatic',
development: process.env.BABEL_ENV === 'development',
},
],
],
// https://babeljs.io/docs/babel-plugin-proposal-decorators#note-compatibility-with-babelplugin-transform-class-properties
plugins: [['@babel/plugin-proposal-decorators', { version: '2023-05' }]],
};
7 changes: 1 addition & 6 deletions components/Git/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ export interface GitLogoProps {

@observer
export class GitLogo extends PureComponent<GitLogoProps> {
constructor(props: GitLogoProps) {
super(props);
makeObservable(this);
}

@observable
path = '';
accessor path = '';

async componentDidMount() {
const { name } = this.props;
Expand Down
2 changes: 1 addition & 1 deletion models/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const larkClient = new HTTPClient({
});

export const githubClient = new HTTPClient({
baseURI: 'https://api.github.com/',
baseURI: isServer() ? 'https://api.github.com/' : `${API_Host}/api/GitHub/`,
responseType: 'json',
}).use(({ request }, next) => {
if (GithubToken)
Expand Down
4 changes: 3 additions & 1 deletion models/Client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BiDataTable, TableCellValue } from 'mobx-lark';
import { BiDataQueryOptions, BiDataTable, TableCellValue } from 'mobx-lark';

import { LarkBaseId, larkClient } from './Base';

Expand All @@ -12,6 +12,8 @@ const CLIENT_TABLE = process.env.NEXT_PUBLIC_CLIENT_TABLE!;
export class ClientModel extends BiDataTable<Client>() {
client = larkClient;

queryOptions: BiDataQueryOptions = { text_field_as_array: false };

constructor(appId = LarkBaseId, tableId = CLIENT_TABLE) {
super(appId, tableId);
}
Expand Down
4 changes: 3 additions & 1 deletion models/Member.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BiDataTable, TableCellValue } from 'mobx-lark';
import { BiDataQueryOptions, BiDataTable, TableCellValue } from 'mobx-lark';

import { LarkBaseId, larkClient } from './Base';

Expand All @@ -22,6 +22,8 @@ export class MemberModel extends BiDataTable<Member>() {

requiredKeys = ['nickname', 'position', 'type', 'skill', 'joinedAt'] as const;

queryOptions: BiDataQueryOptions = { text_field_as_array: false };

constructor(appId = LarkBaseId, tableId = MEMBER_TABLE) {
super(appId, tableId);
}
Expand Down
23 changes: 21 additions & 2 deletions models/Project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { BiDataTable, makeSimpleFilter, TableCellValue } from 'mobx-lark';
import {
BiDataQueryOptions,
BiDataTable,
makeSimpleFilter,
normalizeText,
TableCellLink,
TableCellValue,
TableRecord,
} from 'mobx-lark';
import { NewData } from 'mobx-restful';
import { isEmpty } from 'web-utility';

Expand All @@ -16,7 +24,8 @@ export type Project = Record<
| 'settlementDate'
| 'remark'
| 'image'
| 'openSource',
| 'openSource'
| 'link',
TableCellValue
>;

Expand All @@ -27,6 +36,8 @@ export class ProjectModel extends BiDataTable<Project>() {

sort = { settlementDate: 'DESC' } as const;

queryOptions: BiDataQueryOptions = { text_field_as_array: false };

constructor(appId = LarkBaseId, tableId = PROJECT_TABLE) {
super(appId, tableId);
}
Expand All @@ -39,6 +50,14 @@ export class ProjectModel extends BiDataTable<Project>() {
.filter(Boolean)
.join('&&');
}

normalize({ id, fields: { link, ...fields } }: TableRecord<Project>) {
return {
...fields,
id,
link: link && normalizeText(link as TableCellLink),
};
}
}

export default new ProjectModel();
101 changes: 59 additions & 42 deletions models/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { components } from '@octokit/openapi-types';
import { memoize } from 'lodash';
import { makeObservable, observable } from 'mobx';
import { ListModel, toggle } from 'mobx-restful';
import { averageOf, buildURLData } from 'web-utility';
import { observable } from 'mobx';
import { ListModel, Stream, toggle } from 'mobx-restful';
import { averageOf, buildURLData, mergeStream } from 'web-utility';

import { githubClient } from './Base';

Expand All @@ -13,40 +13,36 @@ export interface GitRepository extends Repository {
}
export type Organization = components['schemas']['organization-full'];

const getGitLanguages = memoize(async (URI: string) => {
const { body: languageCount } = await githubClient.get<
Record<string, number>
>(`repos/${URI}/languages`);
export class RepositoryModel extends Stream<GitRepository>(ListModel) {
client = githubClient;
indexKey = 'full_name' as const;

const languageAverage = averageOf(...Object.values(languageCount!));
organizations = ['idea2app', 'IdeaMall', 'EasyWebApp'];

const languageList = Object.entries(languageCount!)
.filter(([_, score]) => score >= languageAverage)
.sort(([_, a], [__, b]) => b - a);
@observable
accessor currentGroup: GitRepository[] = [];

return languageList.map(([name]) => name);
});
getGitLanguages = memoize(async (URI: string) => {
const { body: languageCount } = await this.client.get<
Record<string, number>
>(`repos/${URI}/languages`);

export class RepositoryModel extends ListModel<GitRepository> {
constructor() {
super();
makeObservable(this);
}
const languageAverage = averageOf(...Object.values(languageCount!));

client = githubClient;
baseURI = 'orgs/idea2app/repos';
indexKey = 'full_name' as const;
const languageList = Object.entries(languageCount!)
.filter(([_, score]) => score >= languageAverage)
.sort(([_, a], [__, b]) => b - a);

@observable
currentGroup: GitRepository[] = [];
return languageList.map(([name]) => name);
});

@toggle('downloading')
async getOne(URI: string) {
const { body } = await this.client.get<Repository>(`repos/${URI}`);

return (this.currentOne = {
...body!,
languages: await getGitLanguages(URI),
languages: await this.getGitLanguages(URI),
});
}

Expand All @@ -56,28 +52,49 @@ export class RepositoryModel extends ListModel<GitRepository> {
));
}

async loadPage(page: number, per_page: number) {
const { body: list } = await this.client.get<Repository[]>(
`${this.baseURI}?${buildURLData({
type: 'public',
sort: 'pushed',
page,
per_page,
})}`,
);
const pageData = await Promise.all(
list!.map(async ({ full_name, ...item }) => ({
...item,
full_name,
languages: await getGitLanguages(full_name),
})),
);
const [_, organization] = this.baseURI.split('/');
async *getRepository(organization: string) {
const per_page = this.pageSize;

this.totalCount ||= 0;
this.totalCount += await this.getRepositoryCount(organization);

for (let page = 1, count = 0; ; page++) {
const { body: list } = await this.client.get<Repository[]>(
`orgs/${organization}/repos?${buildURLData({
type: 'public',
sort: 'pushed',
page,
per_page,
})}`,
);
count += list!.length;

if (count < page * per_page) break;

const pageData = await Promise.all(
list!.map(async ({ full_name, ...item }) => ({
...item,
full_name,
languages: await this.getGitLanguages(full_name),
})),
);
yield* pageData as GitRepository[];
}
}

async getRepositoryCount(organization: string) {
const { body } = await this.client.get<Organization>(
`orgs/${organization}`,
);
return { pageData, totalCount: body!.public_repos };
return body!.public_repos;
}

openStream() {
return mergeStream(
...this.organizations.map(organization =>
this.getRepository.bind(this, organization),
),
);
}
}

Expand Down
1 change: 1 addition & 0 deletions models/Translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const i18n = new TranslationModel({
'zh-CN': zhCN,
'zh-TW': () => import('../translation/zh-TW'),
'zh-HK': () => import('../translation/zh-TW'),
'zh-MO': () => import('../translation/zh-TW'),
'en-US': () => import('../translation/en-US'),
});

Expand Down
43 changes: 23 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,46 @@
"description": "React project scaffold based on TypeScript, Next.js, Bootstrap & Workbox.",
"private": true,
"dependencies": {
"@sentry/nextjs": "^7.91.0",
"@sentry/nextjs": "^7.99.0",
"classnames": "^2.5.1",
"file-type": "^18.7.0",
"idea-react": "^1.0.0-rc.31",
"file-type": "^19.0.0",
"idea-react": "^2.0.0-rc.1",
"koajax": "^0.9.6",
"less": "^4.2.0",
"less-loader": "^11.1.4",
"less-loader": "^12.2.0",
"lodash": "^4.17.21",
"mobx": "~6.10.2",
"mobx-i18n": "^0.4.2",
"mobx-lark": "^1.1.1",
"mobx-react": "~9.0.2",
"mobx-restful": "^0.6.12",
"mobx-restful-table": "^1.2.2",
"next": "^14.0.4",
"mobx": "^6.12.0",
"mobx-i18n": "^0.5.0",
"mobx-lark": "^2.0.0-rc.1",
"mobx-react": "^9.1.0",
"mobx-restful": "^0.7.0-rc.0",
"mobx-restful-table": "^2.0.0-rc.0",
"next": "^14.1.0",
"next-pwa": "~5.6.0",
"next-ssr-middleware": "^0.6.2",
"next-ssr-middleware": "^0.7.0",
"next-with-less": "^3.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.2",
"react-bootstrap": "^2.10.0",
"react-dom": "^18.2.0",
"react-marked-renderer": "^2.0.1",
"web-utility": "^4.1.3",
"webpack": "^5.89.0"
"webpack": "^5.90.0"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@octokit/openapi-types": "^19.1.0",
"@types/lodash": "^4.14.202",
"@types/node": "^18.19.4",
"@types/react": "^18.2.46",
"@types/node": "^18.19.10",
"@types/react": "^18.2.48",
"eslint": "^8.56.0",
"eslint-config-next": "^14.0.4",
"eslint-config-next": "^14.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"husky": "^8.0.3",
"husky": "^9.0.7",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"prettier": "^3.2.4",
"typescript": "~5.3.3"
},
"prettier": {
Expand All @@ -53,7 +56,7 @@
"*.{js,jsx,ts,tsx}": "eslint --fix"
},
"scripts": {
"prepare": "husky install",
"prepare": "husky",
"dev": "next dev",
"build": "next build",
"export": "next build && next export",
Expand Down
3 changes: 3 additions & 0 deletions pages/api/GitHub/[...slug].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { proxyGithub } from './core';

export default proxyGithub();
21 changes: 21 additions & 0 deletions pages/api/GitHub/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { githubClient } from '../../../models/Base';
import { safeAPI } from '../core';

export const proxyGithub = <T>(dataFilter?: (path: string, data: T) => T) =>
safeAPI(async ({ method, url, headers, body }, response) => {
delete headers.host;

const path = url!.slice(`/api/GitHub/`.length);

const { status, body: data } = await githubClient.request<T>({
// @ts-ignore
method,
path,
// @ts-ignore
headers,
body: body || undefined,
});

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

1 comment on commit 34a68d5

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for idea2app ready!

✅ Preview
https://idea2app-55s6jd9tp-techquery.vercel.app

Built with commit 34a68d5.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.