Skip to content

Commit

Permalink
Merge pull request #255 from caorushizi/dev-backend
Browse files Browse the repository at this point in the history
feat: ✨  add backend
  • Loading branch information
caorushizi authored Aug 21, 2024
2 parents 3b8e386 + b7cfed5 commit c4136c8
Show file tree
Hide file tree
Showing 38 changed files with 1,521 additions and 37 deletions.
2 changes: 2 additions & 0 deletions packages/backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
npm-debug.log
7 changes: 7 additions & 0 deletions packages/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.bin
node_modules
build
dist
bin/Logs
release
types
1 change: 1 addition & 0 deletions packages/backend/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
types/
1 change: 1 addition & 0 deletions packages/backend/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
22 changes: 22 additions & 0 deletions packages/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 使用官方 Node.js 镜像作为基础镜像
FROM m.daocloud.io/docker.io/library/node:20

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm install --registry=https://registry.npmmirror.com

# 复制项目文件
COPY . .

RUN npm run build

# 暴露应用运行的端口
EXPOSE 3000

# 启动应用
CMD ["node", "dist/inedx.js"]
30 changes: 30 additions & 0 deletions packages/backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: '3'
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
NODE_ENV: development
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: 123456
MYSQL_DATABASE: mediago
depends_on:
- mysql

mysql:
image: m.daocloud.io/docker.io/library/mysql:5.7
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mediago
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:
16 changes: 16 additions & 0 deletions packages/backend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";

export default [
{
languageOptions: { globals: globals.node },
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-explicit-any": "warn",
},
},
];
1 change: 1 addition & 0 deletions packages/backend/gulpfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { dev, build } from "./scripts";
72 changes: 72 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "backend",
"version": "0.1.0",
"description": "在线视频下载器",
"type": "commonjs",
"main": "dist/index.js",
"scripts": {
"start": "nodemon dist/index.js",
"dev": "cross-env NODE_ENV=development gulp dev",
"build": "cross-env NODE_ENV=production gulp build",
"mysql": "docker-compose up -d mysql",
"mysql:stop": "docker-compose stop mysql",
"types": "tsc",
"types:watch": "tsc -w",
"lint-staged": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^8.57.0",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/fs-extra": "^11.0.4",
"@types/glob": "^8.1.0",
"@types/gulp": "^4.0.17",
"@types/koa": "^2.15.0",
"@types/koa__router": "^12.0.4",
"@types/lodash": "^4.17.4",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.12.12",
"@types/node-fetch": "^2.6.11",
"@types/semver": "^7.5.8",
"@types/serve-handler": "^6.1.4",
"@types/undertaker": "^1.2.11",
"consola": "^3.2.3",
"cross-env": "^7.0.3",
"del": "^7.1.0",
"dotenv": "^16.4.5",
"esbuild": "^0.21.4",
"esbuild-register": "^3.5.0",
"eslint": "^8.57.0",
"glob": "^10.4.1",
"globals": "^15.3.0",
"gulp": "^5.0.0",
"nodemon": "^3.1.4",
"semver": "^7.6.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.10.0"
},
"dependencies": {
"@koa/router": "^12.0.1",
"axios": "^1.7.2",
"cheerio": "1.0.0-rc.12",
"cors": "^2.8.5",
"dayjs": "^1.11.11",
"execa": "^9.1.0",
"fs-extra": "^11.2.0",
"inversify": "^6.0.2",
"koa": "^2.15.3",
"lint-staged": "^15.2.5",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mysql2": "^3.11.0",
"nanoid": "^5.0.7",
"reflect-metadata": "^0.2.2",
"typeorm": "0.3.20"
},
"keywords": []
}
57 changes: 57 additions & 0 deletions packages/backend/scripts/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Env, isDev, mainResolve } from "./utils";
import esbuild from "esbuild";

const external = [
"electron",
"nock",
"aws-sdk",
"mock-aws-s3",
"@cliqz/adblocker-electron-preload",
"node-pty",
];

function getConfig(): esbuild.BuildOptions {
const getDefine = (): Record<string, string> => {
if (isDev) {
return {
__bin__: `"${mainResolve("app/bin").replace(/\\/g, "\\\\")}"`,
};
}

return {
...Env.getInstance().loadDotEnvDefined(),
"process.env.NODE_ENV": '"production"',
};
};

return {
bundle: true,
sourcemap: process.env.NODE_ENV === "development",
external,
define: getDefine(),
outdir: mainResolve("dist"),
loader: { ".png": "file" },
minify: process.env.NODE_ENV === "production",
};
}

function buildOptions(
entry: string,
platform: esbuild.Platform,
target: string,
): esbuild.BuildOptions {
return {
...getConfig(),
entryPoints: [mainResolve(entry)],
platform: platform,
target: [target],
};
}

export function browserOptions(entry: string): esbuild.BuildOptions {
return buildOptions(entry, "browser", "chrome89");
}

export function nodeOptions(entry: string): esbuild.BuildOptions {
return buildOptions(entry, "node", "node16.13");
}
49 changes: 49 additions & 0 deletions packages/backend/scripts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { deleteSync } from "del";
import { mainResolve, Env } from "./utils";
import gulp from "gulp";
import * as esbuild from "esbuild";
import consola from "consola";
import { nodeOptions } from "./config";
// import fs from "fs";

const env = Env.getInstance();
env.loadDotEnvRuntime();

async function clean() {
return deleteSync([mainResolve("app/dist")]);
}

// async function copyBin() {
// const source = mainResolve("bin", process.platform);
// const target = mainResolve("app/bin");
// fs.cpSync(source, target, { recursive: true });
// }

// const copy = gulp.parallel(copyBin);

async function watchTask() {
const main = await esbuild.context(nodeOptions("src/index.ts"));

const watcher = gulp.watch(["./src"]);
watcher
.on("ready", async () => {
await main.rebuild();
})
.on("change", async () => {
await main.rebuild();
})
.on("error", (error) => {
consola.error(error);
});
return Promise.resolve();
}

async function buildTask() {
await esbuild.build(nodeOptions("src/index.ts"));
}

// 开发环境
// TODO 暂时不拷贝 bin 文件夹
export const dev = gulp.series(watchTask);
// 构建打包
export const build = gulp.series(clean, buildTask);
71 changes: 71 additions & 0 deletions packages/backend/scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import dotenv from "dotenv";
import { resolve } from "path";
import fs from "fs-extra";

const baseResolve = (...r: any[]) => resolve(__dirname, ...r);
export const mainResolve = (...r: string[]) => baseResolve("..", ...r);
export const rootResolve = (...r: string[]) => baseResolve("../../..", ...r);

export const isDev = process.env.NODE_ENV === "development";

export const isLinux = process.platform === "linux";
export const isMac = process.platform === "darwin";
export const isWin = process.platform === "win32";

export class Env {
env: Record<string, string> = {};
nodeEnv = "";
static instance: Env;

private constructor(nodeEnv = process.env.NODE_ENV) {
const env = this.parseEnv(rootResolve(".env"));
const modeEnv = this.parseEnv(rootResolve(`.env.${nodeEnv}`));
const localEnv = this.parseEnv(rootResolve(`.env.${nodeEnv}.local`));

this.nodeEnv = nodeEnv || "development";
this.env = { ...env, ...modeEnv, ...localEnv };
}

static getInstance() {
if (!Env.instance) {
Env.instance = new Env();
}

return Env.instance;
}

private parseEnv(path: string) {
if (!fs.existsSync(path)) {
return null;
}

const parsed = dotenv.parse(fs.readFileSync(path));
if (!parsed) {
return null;
}

return Object.keys(parsed).reduce((prev: any, curr) => {
prev[curr] = parsed[curr];
return prev;
}, {});
}

loadDotEnvRuntime() {
Object.keys(this.env).forEach((key) => {
if (process.env[key] != null || !this.env[key]) return;
process.env[key] = this.env[key];
});
}

loadDotEnvDefined() {
return Object.keys(this.env).reduce<Record<string, string>>((prev, cur) => {
if (!cur.startsWith("APP_")) return prev;
prev[`process.env.${[cur]}`] = JSON.stringify(this.env[cur]);
return prev;
}, {});
}

get isDev() {
return this.nodeEnv === "development";
}
}
34 changes: 34 additions & 0 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { inject, injectable } from "inversify";
import { TYPES } from "./types.ts";
import TypeORM from "./vendor/TypeORM.ts";
import RouterHandlerService from "./core/router.ts";
import Koa from "koa";

@injectable()
export default class ElectronApp extends Koa {
constructor(
@inject(TYPES.RouterHandlerService)
private readonly router: RouterHandlerService,
@inject(TYPES.TypeORM)
private readonly db: TypeORM,
) {
super();
}

private async vendorInit() {
await this.db.init();
}

async init(): Promise<void> {
this.router.init();

// vendor
await this.vendorInit();

this.use(this.router.routes()).use(this.router.allowedMethods());

this.listen(3000, () => {
console.log("Server running on port 3000");
});
}
}
18 changes: 18 additions & 0 deletions packages/backend/src/controller/HomeController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { inject, injectable } from "inversify";
import { type Controller } from "../interfaces.ts";
import { TYPES } from "../types.ts";
import FavoriteRepository from "../repository/FavoriteRepository.ts";
import { get } from "../helper/decorator.ts";

@injectable()
export default class HomeController implements Controller {
constructor(
@inject(TYPES.FavoriteRepository)
private readonly favoriteRepository: FavoriteRepository,
) {}

@get("/")
async getFavorites() {
return false;
}
}
Loading

0 comments on commit c4136c8

Please sign in to comment.