diff --git a/.gitignore b/.gitignore index 40af2b4..b908d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ release/* .turbo .env + +pack-dist +app.config.json5.envision diff --git a/config/pacage6/package.json b/config/pacage6/package.json new file mode 100644 index 0000000..1f5018c --- /dev/null +++ b/config/pacage6/package.json @@ -0,0 +1,22 @@ +{ + "name": "codecenter", + "version": "1.0.0", + "scripts": { + "start": "pm2 start dist/app.mjs --name codecenter" + }, + "dependencies": { + "@kevisual/router": "^0.0.10-beta.1", + "@kevisual/use-config": "^1.0.10", + "ioredis": "^5.6.0", + "minio": "^8.0.5", + "pg": "^8.14.1", + "sequelize": "^6.37.6", + "sqlite3": "^5.1.7", + "socket.io": "^4.8.1", + "@msgpack/msgpack": "3.1.1", + "pino": "^9.6.0", + "pino-pretty": "^13.0.0", + "pm2": "^6.0.5", + "dotenv": "^16.4.7" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 8aa245c..e639489 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "main": "index.js", "author": "abearxiong", + "basename": "/root/code-center", "scripts": { "watch": "rollup -c rollup.config.mjs -w", "dev": "cross-env NODE_ENV=development nodemon --delay 2.5 -e js,cjs,mjs --exec node dist/app.mjs", @@ -13,11 +14,14 @@ "build": "rimraf dist && rollup -c rollup.config.mjs", "deploy": "rsync -avz --delete ./dist/ --exclude='app.config.json5' light:~/apps/codecenter/dist", "deploy:sky": "rsync -avz --delete ./dist/ --exclude='app.config.json5' sky:~/kevisual/dist", + "deploy:envision": "rsync -avz --delete ./dist/ --exclude='app.config.json5' envision:~/kevisual/dist", "clean": "rm -rf dist", "reload": "ssh light pm2 restart codecenter", "reload:sky": "ssh sky pm2 restart codecenter", + "reload:envision": "ssh envision pm2 restart codecenter", "pub:me": "npm run build && npm run deploy && npm run reload", "pub:sky": "npm run build && npm run deploy:sky && npm run reload:sky", + "pub:envision": "npm run build && npm run deploy:envision && npm run reload:envision", "start": "pm2 start dist/app.mjs --name codecenter", "release": "node ./config/release/index.mjs", "pub": "envision pack -p -u", @@ -30,9 +34,7 @@ "keywords": [], "types": "types/index.d.ts", "files": [ - "types", - "dist", - "src" + "dist" ], "license": "UNLICENSED", "dependencies": { diff --git a/src/models/user.ts b/src/models/user.ts index b72bd4b..bdc3b6c 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -289,9 +289,19 @@ import { User, UserInit, UserServices } from '@kevisual/code-center-module/models'; export { User, UserInit, UserServices }; -UserInit(null, null, { - alter: true, - logging: false, -}).catch((e) => { - console.error('User sync', e); -}); +import { OrgInit } from '@kevisual/code-center-module/models'; +const init = async () => { + await OrgInit(null, null, { + alter: true, + logging: false, + }).catch((e) => { + console.error('Org sync', e); + }); + await UserInit(null, null, { + alter: true, + logging: false, + }).catch((e) => { + console.error('User sync', e); + }); +}; +init(); diff --git a/src/route.ts b/src/route.ts index 657f3c6..ca8eeb7 100644 --- a/src/route.ts +++ b/src/route.ts @@ -18,6 +18,7 @@ export const addAuth = (app: App) => { .route({ path: 'auth', id: 'auth', + isDebug: true, }) .define(async (ctx) => { const token = ctx.query.token; diff --git a/src/routes/app-manager/domain.ts b/src/routes/app-manager/domain.ts new file mode 100644 index 0000000..d601a5a --- /dev/null +++ b/src/routes/app-manager/domain.ts @@ -0,0 +1,90 @@ +import { app } from '@/app.ts'; +import { AppModel } from './module/app.ts'; +import { AppDomainModel } from './module/app-domain.ts'; + +app + .route({ + path: 'app', + key: 'getDomainApp', + }) + .define(async (ctx) => { + const { domain } = ctx.query.data; + // const query = { + // } + const domainInfo = await AppDomainModel.findOne({ where: { domain } }); + if (!domainInfo) { + ctx.throw(404, 'app not found'); + } + const app = await AppModel.findByPk(domainInfo.appId); + if (!app) { + ctx.throw(404, 'app not found'); + } + ctx.body = app; + return ctx; + }) + .addTo(app); + +app + .route({ + path: 'app-domain', + key: 'create', + middleware: ['auth'], + }) + .define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const uid = tokenUser.uid; + const { domain, appId } = ctx.query.data || {}; + if (!domain || !appId) { + ctx.throw(400, 'domain and appId are required'); + } + const domainInfo = await AppDomainModel.create({ domain, appId, uid }); + ctx.body = domainInfo; + return ctx; + }) + .addTo(app); + +app + .route({ + path: 'app-domain', + key: 'update', + middleware: ['auth'], + }) + .define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const uid = tokenUser.uid; + const { id, domain, appId, status } = ctx.query.data || {}; + if (!domain && !id) { + ctx.throw(400, 'domain and id are required at least one'); + } + if (!status) { + ctx.throw(400, 'status is required'); + } + let domainInfo: AppDomainModel | null = null; + if (id) { + domainInfo = await AppDomainModel.findByPk(id); + } + if (!domainInfo && domain) { + domainInfo = await AppDomainModel.findOne({ where: { domain, appId } }); + } + if (!domainInfo) { + ctx.throw(404, 'domain not found'); + } + if (domainInfo.uid !== uid) { + ctx.throw(403, 'domain must be owned by the user'); + } + if (!domainInfo.checkCanUpdateStatus(status)) { + ctx.throw(400, 'domain status can not be updated'); + } + if (status) { + domainInfo.status = status; + } + + if (appId) { + domainInfo.appId = appId; + } + await domainInfo.save({ fields: ['status', 'appId'] }); + + ctx.body = domainInfo; + return ctx; + }) + .addTo(app); diff --git a/src/routes/app-manager/index.ts b/src/routes/app-manager/index.ts index c51882c..50d0a47 100644 --- a/src/routes/app-manager/index.ts +++ b/src/routes/app-manager/index.ts @@ -2,5 +2,6 @@ import './list.ts'; import './user-app.ts'; import './public/index.ts'; +import './domain.ts'; export * from './module/index.ts'; diff --git a/src/routes/app-manager/list.ts b/src/routes/app-manager/list.ts index 3f963a4..522f60f 100644 --- a/src/routes/app-manager/list.ts +++ b/src/routes/app-manager/list.ts @@ -140,6 +140,7 @@ app path: 'app', key: 'uploadFiles', middleware: ['auth'], + isDebug: true, }) .define(async (ctx) => { try { @@ -264,23 +265,6 @@ app }) .addTo(app); -app - .route({ - path: 'app', - key: 'getDomainApp', - }) - .define(async (ctx) => { - const { domain } = ctx.query.data; - // const query = { - // } - const app = await AppModel.findOne({ where: { domain } }); - if (!app) { - throw new CustomError('app not found'); - } - ctx.body = app; - return ctx; - }) - .addTo(app); app .route({ diff --git a/src/routes/app-manager/module/app-domain.ts b/src/routes/app-manager/module/app-domain.ts new file mode 100644 index 0000000..8532cf0 --- /dev/null +++ b/src/routes/app-manager/module/app-domain.ts @@ -0,0 +1,64 @@ +import { sequelize } from '../../../modules/sequelize.ts'; +import { DataTypes, Model } from 'sequelize'; +export type DomainList = Partial>; + +// 审核,通过,驳回 +const appDomainStatus = ['audit', 'auditReject', 'auditPending', 'running', 'stop'] as const; + +type AppDomainStatus = (typeof appDomainStatus)[number]; +/** + * 应用域名管理 + */ +export class AppDomainModel extends Model { + declare id: string; + declare domain: string; + declare appId: string; + // 状态, + declare status: AppDomainStatus; + declare uid: string; + + declare createdAt: Date; + declare updatedAt: Date; + + checkCanUpdateStatus(newStatus: AppDomainStatus) { + // 原本是运行中,可以改为停止,原本是停止,可以改为运行。 + if (this.status === 'running' || this.status === 'stop') { + return true; + } + // 原本是审核状态,不能修改。 + return false; + } +} + +AppDomainModel.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + comment: 'id', + }, + domain: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + appId: { + type: DataTypes.STRING, + allowNull: false, + }, + uid: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + sequelize, + tableName: 'kv_app_domain', + paranoid: true, + }, +); + +AppDomainModel.sync({ alter: true, logging: false }).catch((e) => { + console.error('AppDomainModel sync', e); +}); diff --git a/src/routes/app-manager/module/app.ts b/src/routes/app-manager/module/app.ts index fb42a70..7d8d205 100644 --- a/src/routes/app-manager/module/app.ts +++ b/src/routes/app-manager/module/app.ts @@ -36,7 +36,6 @@ export class AppModel extends Model { declare title: string; declare description: string; declare version: string; - declare domain: string; declare key: string; declare uid: string; declare pid: string; @@ -69,10 +68,6 @@ AppModel.init( type: DataTypes.STRING, defaultValue: '', }, - domain: { - type: DataTypes.STRING, - defaultValue: '', - }, key: { type: DataTypes.STRING, // 和 uid 组合唯一 diff --git a/src/routes/index.ts b/src/routes/index.ts index 237dd35..c74fb7e 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -11,3 +11,5 @@ import './file/index.ts'; import './micro-app/index.ts'; import './config/index.ts'; + +import './mark/index.ts'; diff --git a/src/routes/mark/index.ts b/src/routes/mark/index.ts new file mode 100644 index 0000000..366cb31 --- /dev/null +++ b/src/routes/mark/index.ts @@ -0,0 +1 @@ +import './list.ts'; \ No newline at end of file diff --git a/src/routes/mark/list.ts b/src/routes/mark/list.ts index 272827b..7f7712e 100644 --- a/src/routes/mark/list.ts +++ b/src/routes/mark/list.ts @@ -44,26 +44,27 @@ app id: markModel.id, }; } else { - const [markModel, created] = await MarkModel.findOrCreate({ - where: { - uid: tokenUser.id, - puid: tokenUser.uid, - title: dayjs().format('YYYY-MM-DD'), - }, - defaults: { - title: dayjs().format('YYYY-MM-DD'), - uid: tokenUser.id, - markType: 'wallnote', - tags: ['daily'], - }, - }); - ctx.body = { - version: Number(markModel.version), - updatedAt: markModel.updatedAt, - createdAt: markModel.createdAt, - id: markModel.id, - created: created, - }; + ctx.throw(400, 'id is required'); + // const [markModel, created] = await MarkModel.findOrCreate({ + // where: { + // uid: tokenUser.id, + // puid: tokenUser.uid, + // title: dayjs().format('YYYY-MM-DD'), + // }, + // defaults: { + // title: dayjs().format('YYYY-MM-DD'), + // uid: tokenUser.id, + // markType: 'wallnote', + // tags: ['daily'], + // }, + // }); + // ctx.body = { + // version: Number(markModel.version), + // updatedAt: markModel.updatedAt, + // createdAt: markModel.createdAt, + // id: markModel.id, + // created: created, + // }; } }) .addTo(app); @@ -87,24 +88,25 @@ app } ctx.body = markModel; } else { + ctx.throw(400, 'id is required'); // id 不存在,获取当天的title为 日期的一条数据 - const [markModel, created] = await MarkModel.findOrCreate({ - where: { - uid: tokenUser.id, - puid: tokenUser.uid, - title: dayjs().format('YYYY-MM-DD'), - }, - defaults: { - title: dayjs().format('YYYY-MM-DD'), - uid: tokenUser.id, - markType: 'wallnote', - tags: ['daily'], - uname: tokenUser.username, - puid: tokenUser.uid, - version: 1, - }, - }); - ctx.body = markModel; + // const [markModel, created] = await MarkModel.findOrCreate({ + // where: { + // uid: tokenUser.id, + // puid: tokenUser.uid, + // title: dayjs().format('YYYY-MM-DD'), + // }, + // defaults: { + // title: dayjs().format('YYYY-MM-DD'), + // uid: tokenUser.id, + // markType: 'wallnote', + // tags: ['daily'], + // uname: tokenUser.username, + // puid: tokenUser.uid, + // version: 1, + // }, + // }); + // ctx.body = markModel; } }) .addTo(app); diff --git a/src/routes/user/me.ts b/src/routes/user/me.ts index fe14e7c..4bb6448 100644 --- a/src/routes/user/me.ts +++ b/src/routes/user/me.ts @@ -12,6 +12,7 @@ export const createCookie = (token: any, ctx: any) => { if (!domain) { return; } + //TODO, 获取访问的 hostname, 如果访问的和 domain 的不一致,也创建cookie const browser = ctx.req.headers['user-agent']; const isBrowser = browser.includes('Mozilla'); // 浏览器 if (isBrowser && ctx.res.cookie) { @@ -50,11 +51,21 @@ export type ReqHeaders = { cookie: string; // 饼干 }; export const getSomeInfoFromReq = (ctx: any) => { - const headers = ctx.req.headers as ReqHeaders; - const userAgent = headers['user-agent']; - const host = headers['host']; - const ip = headers['x-forwarded-for'] || ctx.req.connection.remoteAddress; - console.log('req headers', headers); + const headers = ctx.req?.headers as ReqHeaders; + if (!headers) { + console.log('no req headers', ctx.req); + return { + 'user-agent': '', + browser: '', + isBrowser: false, + host: '', + ip: '', + headers: {}, + }; + } + const userAgent = headers?.['user-agent'] || ''; + const host = headers?.['host']; + const ip = headers?.['x-forwarded-for'] || ctx.req?.connection?.remoteAddress; return { 'user-agent': userAgent, browser: userAgent,