mirror of
https://gitee.com/bitdance-team/chrome-extension
synced 2025-01-10 13:48:14 +08:00
Merge branch 'feat-sst' into develop
This commit is contained in:
commit
3686ddb6c9
@ -2,8 +2,11 @@
|
||||
"root": true,
|
||||
"ignorePatterns": ["**/*"],
|
||||
"plugins": ["@nrwl/nx"],
|
||||
"overrides": [
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
},
|
||||
"overrides": [{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {
|
||||
"@nrwl/nx/enforce-module-boundaries": [
|
||||
@ -11,12 +14,10 @@
|
||||
{
|
||||
"enforceBuildableLibDependency": true,
|
||||
"allow": [],
|
||||
"depConstraints": [
|
||||
{
|
||||
"sourceTag": "*",
|
||||
"onlyDependOnLibsWithTags": ["*"]
|
||||
}
|
||||
]
|
||||
"depConstraints": [{
|
||||
"sourceTag": "*",
|
||||
"onlyDependOnLibsWithTags": ["*"]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,12 +1,12 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
dist
|
||||
tmp
|
||||
out-tsc
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
@ -37,3 +37,5 @@ testem.log
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
.inspirecloud.service.conf
|
||||
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
13
jsconfig.json
Normal file
13
jsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2020",
|
||||
"jsx": "preserve",
|
||||
"strictFunctionTypes": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
3871
package-lock.json
generated
3871
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@byteinspire/api": "^1.0.12",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.0.0",
|
||||
"vue": "^3.0.5"
|
||||
},
|
||||
@ -24,6 +26,9 @@
|
||||
"@nrwl/workspace": "13.5.3",
|
||||
"@nx-plus/vite": "^12.2.0",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/koa": "^2.13.4",
|
||||
"@types/koa__router": "^8.0.11",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/node": "16.11.7",
|
||||
"@typescript-eslint/eslint-plugin": "~5.3.0",
|
||||
"@typescript-eslint/parser": "~5.3.0",
|
||||
|
18
packages/services-api/.eslintrc.json
Normal file
18
packages/services-api/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
11
packages/services-api/README.md
Normal file
11
packages/services-api/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# services-api
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build services-api` to build the library.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test services-api` to execute the unit tests via [Jest](https://jestjs.io).
|
14
packages/services-api/jest.config.js
Normal file
14
packages/services-api/jest.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
displayName: 'services-api',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/packages/services-api',
|
||||
};
|
5
packages/services-api/package.json
Normal file
5
packages/services-api/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@bitdance/services-api",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs"
|
||||
}
|
33
packages/services-api/project.json
Normal file
33
packages/services-api/project.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"root": "packages/services-api",
|
||||
"sourceRoot": "packages/services-api/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/services-api",
|
||||
"main": "packages/services-api/src/index.ts",
|
||||
"tsConfig": "packages/services-api/tsconfig.lib.json",
|
||||
"assets": ["packages/services-api/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["packages/services-api/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/packages/services-api"],
|
||||
"options": {
|
||||
"jestConfig": "packages/services-api/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
1
packages/services-api/src/index.ts
Normal file
1
packages/services-api/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './lib/services-api';
|
7
packages/services-api/src/lib/services-api.spec.ts
Normal file
7
packages/services-api/src/lib/services-api.spec.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { servicesApi } from './services-api';
|
||||
|
||||
describe('servicesApi', () => {
|
||||
it('should work', () => {
|
||||
expect(servicesApi()).toEqual('services-api');
|
||||
});
|
||||
});
|
3
packages/services-api/src/lib/services-api.ts
Normal file
3
packages/services-api/src/lib/services-api.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function servicesApi(): string {
|
||||
return 'services-api';
|
||||
}
|
22
packages/services-api/tsconfig.json
Normal file
22
packages/services-api/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"overrides": [{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/triple-slash-reference": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
|
3
packages/services/.inspirecloudignore
Normal file
3
packages/services/.inspirecloudignore
Normal file
@ -0,0 +1,3 @@
|
||||
**/node_modules
|
||||
.*/**
|
||||
src
|
38
packages/services/gulpfile.js
Normal file
38
packages/services/gulpfile.js
Normal file
@ -0,0 +1,38 @@
|
||||
const {src,dest,parallel,watch,series} = require('gulp');
|
||||
const ts = require('gulp-typescript');
|
||||
const tsProject = ts.createProject("tsconfig.app.json");
|
||||
const shell = require('gulp-shell');
|
||||
const nodemon = require('gulp-nodemon');
|
||||
|
||||
function buildTs () {
|
||||
return src('./src/**/*.ts')
|
||||
.pipe(tsProject())
|
||||
.pipe(dest('./dist'));
|
||||
}
|
||||
|
||||
function start (done) {
|
||||
nodemon({
|
||||
exec: 'inspirecloud dev',
|
||||
ext: 'js,ts',
|
||||
ignore: './dist',
|
||||
delay: 50,
|
||||
tasks: ['buildTs'],
|
||||
done
|
||||
})
|
||||
}
|
||||
|
||||
function watchServe () {
|
||||
return watch('src/**/*.ts', {
|
||||
delay: 100
|
||||
}, series(buildTs))
|
||||
}
|
||||
|
||||
function serve () {
|
||||
return series(buildTs, start)()
|
||||
}
|
||||
|
||||
function deploy () {
|
||||
return series(buildTs, shell.task('inspirecloud deploy'))()
|
||||
}
|
||||
|
||||
module.exports = {serve,deploy,buildTs}
|
3
packages/services/index.js
Normal file
3
packages/services/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
const { app } = require('./dist/app')
|
||||
// 导出 HTTP handler, koa 对象不可直接作为 HTTP handler, 需要调用 callback() 获取
|
||||
module.exports = app.callback()
|
4
packages/services/inspirecloud.json
Normal file
4
packages/services/inspirecloud.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "index.js",
|
||||
"watch": ["index.js"]
|
||||
}
|
5580
packages/services/package-lock.json
generated
Normal file
5580
packages/services/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,33 @@
|
||||
{
|
||||
"name": "@bitdance/services",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs"
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"dev": "gulp serve",
|
||||
"deploy": "gulp deploy",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@byteinspire/api": "^1.0.12",
|
||||
"@byteinspire/cli": "^2.2.23",
|
||||
"@koa/router": "^10.1.1",
|
||||
"koa": "^2.13.4",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-bunyan-logger": "^2.1.0",
|
||||
"koa-helmet": "^6.1.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa-swagger-decorator": "^1.8.2",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/koa-bunyan-logger": "^2.1.4",
|
||||
"@types/koa2-cors": "^2.0.2",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-cli": "^2.3.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp-shell": "^0.8.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"nodemon": "^2.0.15"
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,4 @@
|
||||
{
|
||||
"root": "packages/services",
|
||||
"sourceRoot": "packages/services/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/services",
|
||||
"main": "packages/services/src/index.ts",
|
||||
"tsConfig": "packages/services/tsconfig.lib.json",
|
||||
"assets": ["packages/services/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["packages/services/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/packages/services"],
|
||||
"options": {
|
||||
"jestConfig": "packages/services/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
|
55
packages/services/src/app.ts
Normal file
55
packages/services/src/app.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import Koa from 'koa'
|
||||
import koaBody from 'koa-body';
|
||||
import router from './router';
|
||||
import helmet from 'koa-helmet'
|
||||
import koaBunyanLogger from 'koa-bunyan-logger'
|
||||
import koaCors from 'koa2-cors';
|
||||
import { errorHandler } from './utils/response'
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
// 跨域请求设置
|
||||
app.use(koaCors({
|
||||
origin: function (ctx) { //设置允许来自指定域名请求
|
||||
return '*'
|
||||
},
|
||||
maxAge: 5, //指定本次预检请求的有效期,单位为秒。
|
||||
credentials: true, //是否允许发送Cookie
|
||||
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法'
|
||||
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-tt-session-v2'], //设置服务器支持的所有头信息字段
|
||||
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
|
||||
}))
|
||||
|
||||
// 过滤不安全的请求内容
|
||||
// app.use(helmet.contentSecurityPolicy());
|
||||
app.use(helmet.dnsPrefetchControl());
|
||||
app.use(helmet.expectCt());
|
||||
app.use(helmet.frameguard());
|
||||
app.use(helmet.hidePoweredBy());
|
||||
app.use(helmet.hsts());
|
||||
app.use(helmet.ieNoOpen());
|
||||
app.use(helmet.noSniff());
|
||||
app.use(helmet.permittedCrossDomainPolicies());
|
||||
app.use(helmet.referrerPolicy());
|
||||
app.use(helmet.xssFilter());
|
||||
|
||||
// 解析不同类别的请求
|
||||
app.use(koaBody({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
maxFileSize: 30 * 1024 * 1024
|
||||
}
|
||||
}));
|
||||
|
||||
// 请求日志
|
||||
app.use(koaBunyanLogger());
|
||||
app.use(koaBunyanLogger.requestIdContext());
|
||||
app.use(koaBunyanLogger.requestLogger());
|
||||
|
||||
|
||||
// 若后面的路由抛错,则封装为错误响应返回
|
||||
app.use(errorHandler);
|
||||
|
||||
// 为应用使用路由定义
|
||||
app.use(router.routes()).use(router.allowedMethods());
|
||||
export { app }
|
56
packages/services/src/controllers/auth.ts
Normal file
56
packages/services/src/controllers/auth.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { getCurrentUser, login, logout, register } from '../services/user';
|
||||
import { Context, description, middlewares, request, security, securityAll, summary, swaggerClass, swaggerProperty, tagsAll } from 'koa-swagger-decorator';
|
||||
import { authenticate, swaggerBody } from '../utils/swagger';
|
||||
import { isNil } from 'lodash';
|
||||
import { authentication } from '../utils/response';
|
||||
@swaggerClass()
|
||||
export class LoginRegDto {
|
||||
@swaggerProperty({ type: "string", required: true }) username = "";
|
||||
@swaggerProperty({ type: "string", required: true }) password = "";
|
||||
};
|
||||
|
||||
@tagsAll('认证系统')
|
||||
export default class Auth {
|
||||
|
||||
@request('POST', '/auth/register')
|
||||
@summary('注册一个用户')
|
||||
@swaggerBody(LoginRegDto)
|
||||
static async register(ctx: Context) {
|
||||
const params = ctx.validatedBody as LoginRegDto;
|
||||
const data = await register(
|
||||
ctx,
|
||||
params.username,
|
||||
params.password
|
||||
);
|
||||
ctx.body = { code: 200, msg: '' }
|
||||
}
|
||||
|
||||
@request('POST', '/auth/login')
|
||||
@summary('登录一个用户')
|
||||
@swaggerBody(LoginRegDto)
|
||||
static async login(ctx: Context) {
|
||||
const params = ctx.validatedBody as LoginRegDto;
|
||||
const data = await login(
|
||||
ctx,
|
||||
params.username,
|
||||
params.password
|
||||
);
|
||||
ctx.body = { code: 200, msg: '' }
|
||||
}
|
||||
|
||||
@request('GET', '/auth/info')
|
||||
@summary('获取当前的用户信息')
|
||||
@authenticate
|
||||
static async info(ctx: Context) {
|
||||
const data = await getCurrentUser(ctx)
|
||||
ctx.body = { code: 200, msg: '', data };
|
||||
}
|
||||
|
||||
@request('POST', '/auth/logout')
|
||||
@summary('登出当前系统')
|
||||
@security([{ session: [] }])
|
||||
static async logout(ctx: Context) {
|
||||
await logout(ctx)
|
||||
ctx.body = { code: 200, msg: '' };
|
||||
}
|
||||
}
|
29
packages/services/src/controllers/files.ts
Normal file
29
packages/services/src/controllers/files.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { getCurrentUser, login, logout, register } from '../services/user';
|
||||
import { Context, description, middlewares, formData, request, security, securityAll, summary, swaggerClass, swaggerProperty, tagsAll } from 'koa-swagger-decorator';
|
||||
import { swaggerBody } from '../utils/swagger';
|
||||
import { isArray, isNil, uniqueId } from 'lodash';
|
||||
import { authentication } from '../utils/response';
|
||||
import { uploadFile } from '../services/files';
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
@tagsAll('文件系统')
|
||||
export default class RemoteFile {
|
||||
|
||||
@request('POST', '/files')
|
||||
@summary('上传文件')
|
||||
@formData({ file: { type: 'file', required: true, description: '文件内容' } })
|
||||
@security([{ session: [] }])
|
||||
@middlewares([authentication])
|
||||
static async upload(ctx: Context) {
|
||||
const file = ctx.request.files?.['file']
|
||||
|
||||
if (isNil(file)) throw { code: 400, msg: '未上传任何文件' }
|
||||
if (isArray(file)) throw { code: 400, msg: '只能上传一个文件' }
|
||||
|
||||
const content = readFileSync(file.path)
|
||||
const data = await uploadFile(file.name || uniqueId('file'), content)
|
||||
|
||||
ctx.body = { code: 200, msg: '', data }
|
||||
}
|
||||
|
||||
}
|
19
packages/services/src/controllers/index.ts
Normal file
19
packages/services/src/controllers/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Context, description, middlewares, request, security, summary, swaggerClass, swaggerProperty, tagsAll } from 'koa-swagger-decorator';
|
||||
import { authentication } from '../utils/response';
|
||||
import { authenticate, swaggerBody } from '../utils/swagger';
|
||||
|
||||
@tagsAll('测试')
|
||||
export default class Index {
|
||||
@request('GET', '/test')
|
||||
@summary('测试服务器是否正常')
|
||||
static async test(ctx: Context) {
|
||||
ctx.body = { code: 200, msg: '', data: new Date() }
|
||||
}
|
||||
|
||||
@request('GET', '/authtest')
|
||||
@summary('测试服务器是否正常')
|
||||
@authenticate
|
||||
static async authtest(ctx: Context) {
|
||||
ctx.body = { code: 200, msg: '', data: new Date() }
|
||||
}
|
||||
}
|
50
packages/services/src/controllers/memo.ts
Normal file
50
packages/services/src/controllers/memo.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { getCurrentUser } from './../services/user';
|
||||
import { Context, description, middlewares, path, request, security, summary, swaggerClass, swaggerProperty, tagsAll } from 'koa-swagger-decorator';
|
||||
import { authentication } from '../utils/response';
|
||||
import { authenticate, swaggerBody } from '../utils/swagger';
|
||||
import { ObjectId, UserContext } from '../models/base';
|
||||
import memoService from '../services/memo';
|
||||
import { multipageHttpQuery } from '../utils/multipage';
|
||||
import { User } from '../models/user';
|
||||
|
||||
export class CreateMemoDto {
|
||||
@swaggerProperty({ type: "string", required: true }) content = "";
|
||||
};
|
||||
|
||||
@tagsAll('备忘录')
|
||||
export default class Memo {
|
||||
|
||||
@request('GET', '/memos')
|
||||
@summary('获取个人所有的备忘录')
|
||||
@authenticate
|
||||
@multipageHttpQuery()
|
||||
static async getAll(ctx: UserContext) {
|
||||
const data = await memoService.findByUserPage(ctx.user._id, ctx.validatedQuery)
|
||||
ctx.body = { code: 200, msg: '', data }
|
||||
}
|
||||
|
||||
@request('POST', '/memos')
|
||||
@summary('创建一个备忘录')
|
||||
@authenticate
|
||||
@swaggerBody(CreateMemoDto)
|
||||
static async createOne(ctx: UserContext) {
|
||||
// ctx.user 获取的只是一个JSON字段,不支持数据库关联
|
||||
const user = await memoService.db.table<User>('_user').where({ _id: new ObjectId(ctx.user._id) }).projection({ username: 1, lastLogin: 1, status: 1 }).findOne()
|
||||
|
||||
const data = await memoService.createOne({
|
||||
content: ctx.validatedBody.content,
|
||||
user
|
||||
})
|
||||
ctx.body = { code: 200, msg: '', data }
|
||||
}
|
||||
|
||||
@request('DELETE', '/memos/{id}')
|
||||
@summary('删除一个备忘录')
|
||||
@authenticate
|
||||
@path({ id: { type: 'string', required: true, description: 'ID' } })
|
||||
static async deleteOne(ctx: UserContext) {
|
||||
await memoService.deleteOne(ctx.validatedParams.id)
|
||||
ctx.body = { code: 200, msg: '' }
|
||||
}
|
||||
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './lib/services';
|
@ -1,7 +0,0 @@
|
||||
import { services } from './services';
|
||||
|
||||
describe('services', () => {
|
||||
it('should work', () => {
|
||||
expect(services()).toEqual('services');
|
||||
});
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
export function services(): string {
|
||||
return 'services';
|
||||
}
|
15
packages/services/src/models/base.ts
Normal file
15
packages/services/src/models/base.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Context } from 'koa-swagger-decorator';
|
||||
export type WithId<T extends object> = T & { _id: string }
|
||||
|
||||
import inspirecloud from '@byteinspire/api';
|
||||
import { User } from './user';
|
||||
|
||||
export const ObjectId = inspirecloud.db.ObjectId
|
||||
|
||||
export type ObjectIdType = InstanceType<typeof ObjectId>
|
||||
|
||||
export interface PageQuery { page: number, pageSize: number }
|
||||
|
||||
export interface UserContext extends Context {
|
||||
user: WithId<User>
|
||||
}
|
8
packages/services/src/models/memo.ts
Normal file
8
packages/services/src/models/memo.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { IEntity } from '@byteinspire/db';
|
||||
import { User } from './user';
|
||||
import { ObjectIdType } from './base';
|
||||
|
||||
export interface Memo {
|
||||
content: string;
|
||||
user: User
|
||||
}
|
17
packages/services/src/models/user.ts
Normal file
17
packages/services/src/models/user.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export interface User {
|
||||
avatar: string | null;
|
||||
email: string | null;
|
||||
phoneNumber: string | null;
|
||||
intro: string | null;
|
||||
lastLogin: number;
|
||||
loginCount: number;
|
||||
lastIp: string;
|
||||
status: boolean;
|
||||
createAt: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
username: string;
|
||||
firstProvider: string;
|
||||
loginAt: number;
|
||||
expireAt: number;
|
||||
}
|
32
packages/services/src/router.ts
Normal file
32
packages/services/src/router.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import '@koa/router'
|
||||
|
||||
import { SwaggerRouter } from 'koa-swagger-decorator'
|
||||
import path from 'path'
|
||||
|
||||
const router = new SwaggerRouter({ prefix: '/api' }) // extends from koa-router
|
||||
|
||||
// swagger docs avaliable at http://localhost:3000/api/swagger-html
|
||||
router.swagger({
|
||||
title: 'BitDance浏览器插件后台服务',
|
||||
description: '请求认证头为 `x-tt-session-v2`',
|
||||
version: '1.0.0',
|
||||
prefix: '/api',
|
||||
swaggerHtmlEndpoint: '/swagger-html',
|
||||
swaggerJsonEndpoint: '/swagger-json',
|
||||
swaggerOptions: {
|
||||
securityDefinitions: {
|
||||
session: {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'x-tt-session-v2',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// mapDir will scan the input dir, and automatically call router.map to all Router Class
|
||||
router.mapDir(path.resolve(__dirname, 'controllers'), {
|
||||
ignore: ["**.spec.ts", "**.d.ts"],
|
||||
})
|
||||
|
||||
export default router
|
108
packages/services/src/services/base.ts
Normal file
108
packages/services/src/services/base.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { isNil } from 'lodash';
|
||||
import { IEntity, ITable, API } from '@byteinspire/db';
|
||||
import { Memo } from '../models/memo';
|
||||
import { Context } from 'koa-swagger-decorator';
|
||||
import inspirecloud from '@byteinspire/api'
|
||||
import { multipageDbQuery } from '../utils/multipage';
|
||||
import { PageQuery, ObjectIdType, ObjectId } from '../models/base';
|
||||
|
||||
export default class BaseDbService<T> {
|
||||
table: ITable<T>
|
||||
db: API = inspirecloud.db
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name 表名
|
||||
*/
|
||||
constructor(name: string) {
|
||||
this.table = inspirecloud.db.table<T>(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找多个
|
||||
* @returns
|
||||
*/
|
||||
findMany(query: any): Promise<(T & IEntity)[]> {
|
||||
return query.find()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找全部
|
||||
* @returns
|
||||
*/
|
||||
findAll(): Promise<(T & IEntity)[]> {
|
||||
return this.findMany(this.table.where())
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单个
|
||||
* @returns
|
||||
*/
|
||||
findOne(id: string): Promise<(T & IEntity)> {
|
||||
const result = this.table.where({ _id: new ObjectId(id) }).findOne()
|
||||
if (isNil(result)) throw { status: 404, message: 'Not Found' }
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查找
|
||||
* @returns
|
||||
*/
|
||||
findPage(pageArg: PageQuery, query: any) {
|
||||
return multipageDbQuery(pageArg, query)
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页全部查找
|
||||
* @param pageArg
|
||||
* @returns
|
||||
*/
|
||||
findAllPage(pageArg: PageQuery) {
|
||||
return this.findPage(pageArg, this.table.where())
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个
|
||||
* @returns
|
||||
*/
|
||||
createOne(item: T): Promise<(T & IEntity)> {
|
||||
return this.table.save(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个
|
||||
* @returns
|
||||
*/
|
||||
async updateOne(item: Partial<T> & { _id: string }): Promise<T & IEntity> {
|
||||
const result = await this.findOne(item._id)
|
||||
|
||||
for (const [k, v] of Object.entries(item) as [keyof (T & IEntity), any][]) {
|
||||
if (k === '_id' || k === 'createdAt' || k === 'updatedAt') continue
|
||||
result[k] = v
|
||||
}
|
||||
return this.table.save(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
* @param id
|
||||
*/
|
||||
async deleteOne(id: string) {
|
||||
const result = await this.table.where({ _id: new ObjectId(id) }).findOne()
|
||||
if (isNil(result)) throw { status: 404, message: 'Not Found' }
|
||||
return await this.table.delete([result])
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除多个数据
|
||||
* @param ids
|
||||
* @returns 成功删除数据的数目
|
||||
*/
|
||||
async deleteMany(ids: string[]) {
|
||||
const result = await this.table.where({ _id: this.db.in(ids) }).find()
|
||||
const delRes = await this.table.delete(result)
|
||||
return (delRes as any).deletedCount
|
||||
}
|
||||
|
||||
}
|
||||
|
14
packages/services/src/services/files.ts
Normal file
14
packages/services/src/services/files.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import inspirecloud from '@byteinspire/api';
|
||||
|
||||
const fileService = inspirecloud.file
|
||||
|
||||
const uploadFile = fileService.upload.bind(fileService) as any as (
|
||||
name: string,
|
||||
buffer: Buffer | string | { url: string },
|
||||
) => Promise<{ url: string; id: string }>;
|
||||
|
||||
const downloadFile = fileService.download.bind(fileService)
|
||||
|
||||
const removeFile = fileService.delete.bind(fileService)
|
||||
|
||||
export { uploadFile, downloadFile, removeFile }
|
19
packages/services/src/services/memo.ts
Normal file
19
packages/services/src/services/memo.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { getCurrentUser } from './user';
|
||||
import { ObjectId } from './../models/base';
|
||||
import { Memo } from './../models/memo';
|
||||
import BaseDbService from './base'
|
||||
import { IEntity, ITable, API } from '@byteinspire/db';
|
||||
import { PageQuery } from '../models/base';
|
||||
|
||||
class MemoService extends BaseDbService<Memo> {
|
||||
findByUserPage(uid: string, pageArg: PageQuery) {
|
||||
return this.findPage(pageArg, this.table.where({ user: new ObjectId(uid) }).populate({
|
||||
ref: 'user',
|
||||
projection: ['_id', 'username', 'nickname', 'lastLogin', 'status']
|
||||
} as any))
|
||||
}
|
||||
}
|
||||
|
||||
const memoService = new MemoService('memo')
|
||||
|
||||
export default memoService
|
25
packages/services/src/services/user.ts
Normal file
25
packages/services/src/services/user.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { User } from './../models/user';
|
||||
import { WithId } from '../models/base';
|
||||
import { Context } from 'koa-swagger-decorator';
|
||||
import inspirecloud from '@byteinspire/api';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
const userService = (inspirecloud as any).user
|
||||
|
||||
export const login = userService.login as (ctx: Context, username: string, password: string) => Promise<undefined>
|
||||
|
||||
export const register = userService.register as (ctx: Context, username: string, password: string) => Promise<undefined>
|
||||
|
||||
export const getCurrentUser = userService.current as (ctx: Context) => Promise<WithId<User> | undefined>
|
||||
|
||||
export const logout = userService.logout as (ctx: Context) => Promise<undefined>
|
||||
|
||||
export const updateNewPassword = userService.changePassword as (context: Context, newPassword: string, originPassword?: string) => Promise<any>;
|
||||
|
||||
export async function isLogin(ctx: Context) {
|
||||
try {
|
||||
return !isNil(await getCurrentUser(ctx))
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
27
packages/services/src/utils/multipage.ts
Normal file
27
packages/services/src/utils/multipage.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Context, query } from 'koa-swagger-decorator';
|
||||
import { PageQuery } from '../models/base';
|
||||
|
||||
// 用于分页的数据库查询辅助函数
|
||||
export async function multipageDbQuery<T = any>(pageArg: PageQuery, query: any) {
|
||||
const { page = 1, pageSize = 10 } = pageArg
|
||||
|
||||
if (page < 1 || pageSize < 1) throw { status: 400, message: '分页参数不合法' }
|
||||
|
||||
const total = await query.count();
|
||||
const content = await query.skip((page - 1) * pageSize).limit(pageSize).find();
|
||||
return {
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
content: (content || []) as T[]
|
||||
}
|
||||
}
|
||||
|
||||
//用于分页的HTTP Query查询
|
||||
export function multipageHttpQuery() {
|
||||
return query(
|
||||
{
|
||||
page: { type: 'number', required: false, default: 1, description: 'type' },
|
||||
pageSize: { type: 'number', required: false, default: 10, description: 'type' }
|
||||
})
|
||||
}
|
35
packages/services/src/utils/response.ts
Normal file
35
packages/services/src/utils/response.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Context } from 'koa-swagger-decorator';
|
||||
import { UserContext } from '../models/base';
|
||||
import { getCurrentUser, isLogin } from '../services/user';
|
||||
|
||||
// 处理请求中的错误
|
||||
export async function errorHandler(ctx: Context, next: any) {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const error = err as any
|
||||
// 抛出的错误可以附带 status 字段,代表 http 状态码
|
||||
// 若没有提供,则默认状态码为 500,代表服务器内部错误
|
||||
ctx.status = error.status || 500;
|
||||
ctx.body = { code: ctx.status, msg: error.message };
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前的请求进行登录验证,并注入用户信息到 ctx.user
|
||||
* @param ctx 请求上下文
|
||||
* @param next
|
||||
*/
|
||||
export async function authentication(ctx: UserContext, next: any) {
|
||||
try {
|
||||
const user = await getCurrentUser(ctx)
|
||||
if (!user) throw ""
|
||||
ctx.user = user
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw { status: 401, message: "用户未登录" }
|
||||
}
|
||||
await next()
|
||||
}
|
24
packages/services/src/utils/swagger.ts
Normal file
24
packages/services/src/utils/swagger.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { body, security } from 'koa-swagger-decorator'
|
||||
import { authentication } from './response'
|
||||
|
||||
export function swaggerDocument(cls: any) {
|
||||
return (cls as any).swaggerDocument
|
||||
}
|
||||
|
||||
export function swaggerBody<T extends object>(cls: T) {
|
||||
return body(swaggerDocument(cls))
|
||||
}
|
||||
|
||||
export function authenticate(
|
||||
target: any,
|
||||
name: string,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
|
||||
if (!descriptor.value.middlewares) descriptor.value.middlewares = []
|
||||
descriptor.value.middlewares.push(authentication)
|
||||
|
||||
// call other decorators
|
||||
security([{ session: [] }])(target, name, descriptor)
|
||||
return descriptor;
|
||||
};
|
10
packages/services/tsconfig.app.json
Normal file
10
packages/services/tsconfig.app.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"experimentalDecorators": true,
|
||||
"types": []
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"exclude": ["**/*.spec.ts"]
|
||||
}
|
@ -5,18 +5,18 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"strictFunctionTypes": false,
|
||||
"experimentalDecorators": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"declaration": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
// "files": [],
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{
|
||||
"path": "./tsconfig.app.json"
|
||||
}]
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
"composite": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
@ -16,7 +17,7 @@
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@bitdance/services": ["packages/services/src/index.ts"],
|
||||
"@bitdance/services-api": ["packages/services-api/src/index.ts"],
|
||||
"@bitdance/shared": ["packages/shared/src/index.ts"]
|
||||
}
|
||||
},
|
||||
|
@ -3,6 +3,7 @@
|
||||
"projects": {
|
||||
"plugin-ui": "packages/plugin-ui",
|
||||
"services": "packages/services",
|
||||
"services-api": "packages/services-api",
|
||||
"shared": "packages/shared",
|
||||
"shell-chrome": "packages\\shell-chrome"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user