Skip to content
This repository has been archived by the owner on Jun 6, 2021. It is now read-only.

[Draft] 日常招新 #23

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
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
6 changes: 6 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
jest.config.js
dist
.eslintrc.js
scripts
test
80 changes: 80 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'quotes': [
'warn',
'single',
{
'avoidEscape': true
}
],
'semi': [
'warn',
'always'
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/quotes': [
'warn',
'single',
{
'avoidEscape': true
}
],
'@typescript-eslint/semi': [
'warn',
'always'
],
'import/order': [
'error',
{
'groups': [
'builtin',
'external',
'parent',
'sibling',
'index'
],
'newlines-between': 'always',
'alphabetize': {
'order': 'asc',
'caseInsensitive': true
}
}
]
},
env: {
node: true,
browser: false,
es6: true
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
typescript: {
alwaysTryTypes: true
},
}
}
};
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:12-alpine
FROM node:alpine

WORKDIR /usr/src/backend

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

backend of recruitment system for Unique Studio

## Tool Chain
## Tech stack

1. `node.js`
2. `typescript`
Expand Down
38 changes: 25 additions & 13 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
const tsconfig = require('./tsconfig.json');

const paths = tsconfig.compilerOptions.paths;

const moduleNameMapper = Object.keys(paths).reduce((acc, curr) => {
return {
...acc,
[`^${curr.match(/@.*\//)[0]}(.*)$`]: '<rootDir>' + paths[curr][0].replace('*', '$1'),
};
}, {});

module.exports = {
"roots": [
"<rootDir>/test"
roots: [
'<rootDir>/test',
],
"transform": {
"^.+\\.ts?$": "ts-jest"
transform: {
'^.+\\.ts?$': 'ts-jest',
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts?$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts?$',
moduleFileExtensions: [
'ts',
'tsx',
'js',
'jsx',
'json',
'node',
],
"testEnvironment": "node"
testEnvironment: 'node',
moduleNameMapper,
};
59 changes: 32 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,54 @@
"acm-client": "^3.1.0",
"cron": "^1.8.2",
"express": "^4.17.1",
"express-validator": "^6.6.0",
"express-validator": "^6.6.1",
"ioredis": "^4.19.2",
"jsonwebtoken": "^8.5.1",
"mkdirp": "^1.0.4",
"moment": "^2.27.0",
"mongodb": "^3.5.9",
"mongoose": "5.7.5",
"mongodb": "^3.6.3",
"mongoose": "^5.10.13",
"morgan": "^1.10.0",
"multer": "^1.4.2",
"node-fetch": "^2.6.0",
"nodemailer": "^6.4.10",
"redis": "^3.0.2",
"socket.io": "^2.3.0"
"node-fetch": "^2.6.1",
"nodemailer": "^6.4.15",
"socket.io": "^2.3.0",
"winston": "^3.3.3"
},
"devDependencies": {
"@types/cron": "^1.7.2",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.3",
"@types/express": "^4.17.8",
"@types/ioredis": "^4.17.7",
"@types/jest": "^26.0.15",
"@types/jsonwebtoken": "^8.5.0",
"@types/mkdirp": "^1.0.1",
"@types/mongodb": "^3.5.25",
"@types/mongoose": "5.5.6",
"@types/multer": "^1.4.3",
"@types/node": "^14.0.14",
"@types/mongodb": "^3.5.33",
"@types/mongoose": "^5.7.37",
"@types/morgan": "^1.9.2",
"@types/multer": "^1.4.4",
"@types/node": "^14.14.6",
"@types/node-fetch": "^2.5.7",
"@types/nodemailer": "^6.4.0",
"@types/redis": "^2.8.22",
"@types/socket.io": "^2.1.8",
"@types/socket.io-client": "^1.4.33",
"@types/supertest": "^2.0.9",
"jest": "^26.1.0",
"@types/socket.io": "^2.1.11",
"@types/socket.io-client": "^1.4.34",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"eslint": "^7.13.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-plugin-import": "^2.22.1",
"jest": "^26.6.3",
"socket.io-client": "^2.3.0",
"supertest": "^4.0.2",
"ts-jest": "^26.1.1",
"ts-node": "^8.10.2",
"supertest": "^6.0.1",
"ts-jest": "^26.4.3",
"ts-node": "^9.0.0",
"tsc-alias": "^1.1.1",
"tsconfig-paths": "^3.9.0",
"tslint": "^6.1.2",
"typescript": "^3.9.5"
"typescript": "^4.0.5"
},
"scripts": {
"build": "tsc",
"lint": "eslint . --fix",
"postbuild": "ts-node scripts/resolveAlias.ts",
"test": "jest --forceExit",
"startProd": "node build/src/index.js",
"startDev": "ts-node -r tsconfig-paths/register --files src/index.ts"
}
}
}
29 changes: 15 additions & 14 deletions src/actions/candidate/addCandidate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { RequestHandler } from 'express';
import { body, validationResult } from 'express-validator';
import path from 'path';
import { io } from '../../app';

import { GENDERS, GRADES, GROUPS_, RANKS } from '@config/consts';
import { body, validationResult } from 'express-validator';

import { sendSMS } from '@actions/candidate/sendSMS';
import { GENDERS, GRADES, GROUPS_, RANKS, TITLE_REGEX } from '@config/consts';
import { Candidate, Handler } from '@config/types';
import { CandidateRepo, RecruitmentRepo } from '@database/model';
import { titleConverter } from '@utils/titleConverter';
import { io } from '@servers/websocket';
import { copyFile } from '@utils/copyFile';
import { errorRes } from '@utils/errorRes';
import { logger } from '@utils/logger';
import sendEmail from '@utils/sendEmail';
import { sendSMS } from './sendSMS';
import { titleConverter } from '@utils/titleConverter';

export const addCandidate: RequestHandler = async (req, res, next) => {
export const addCandidate: Handler<Candidate> = async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
Expand All @@ -23,7 +24,7 @@ export const addCandidate: RequestHandler = async (req, res, next) => {
if (req.file) {
const { originalname: filename, path: oldPath } = req.file;
filepath = path.join('./data/resumes', title, group);
filepath = await copyFile(oldPath, filepath, `${name} - ${filename}`);
filepath = copyFile(oldPath, filepath, `${name} - ${filename}`);
}
const info = await CandidateRepo.createAndInsert({
name,
Expand All @@ -39,12 +40,12 @@ export const addCandidate: RequestHandler = async (req, res, next) => {
isQuick,
title,
resume: filepath,
referrer
referrer,
});
await RecruitmentRepo.update({ title, 'groups.name': group }, {
'groups.$.total': await CandidateRepo.count({ title, group }),
'groups.$.steps.0': await CandidateRepo.count({ title, group, step: 0 }),
'total': await CandidateRepo.count({ title })
'total': await CandidateRepo.count({ title }),
});
res.json({ type: 'success' });
io.emit('addCandidate', { candidate: info });
Expand All @@ -67,7 +68,7 @@ export const addCandidate: RequestHandler = async (req, res, next) => {
}
};

export const verifyTitle = body('title').matches(/\d{4}[ASC]/, 'g').withMessage('Title is invalid!')
export const verifyTitle = body('title').matches(TITLE_REGEX).withMessage('Title is invalid!')
.custom(async (title) => {
const recruitment = (await RecruitmentRepo.query({ title }))[0];
if (!recruitment) {
Expand All @@ -90,8 +91,8 @@ export const addCandidateVerify = [
body('gender').isInt({ lt: GENDERS.length, gt: -1 }).withMessage('Gender is invalid!'),
body('isQuick').isBoolean().withMessage('IsQuick is invalid!'),
body('phone').isMobilePhone('zh-CN').withMessage('Phone is invalid!'),
body('phone').custom(async (phone, { req }) => {
if ((await CandidateRepo.query({ phone, title: req.body.title })).length !== 0) {
body('phone').custom(async (phone, { req: { body: { title } } }) => {
if ((await CandidateRepo.query({ phone, title })).length !== 0) {
throw new Error('You have already applied!');
}
}),
Expand All @@ -103,5 +104,5 @@ export const addCandidateVerify = [
body('rank').isInt({ lt: RANKS.length, gt: -1 }).withMessage('Rank is invalid!'),
body('intro').isString().withMessage('Intro is invalid!'),
body('referrer').isString().withMessage('Referrer is invalid!'),
verifyTitle
verifyTitle,
];
4 changes: 2 additions & 2 deletions src/actions/candidate/addComment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Socket } from 'socket.io';

import { io } from '../../app';
import { Comment } from '@config/types';
import { CandidateRepo, UserRepo } from '@database/model';
import { io } from '@servers/websocket';
import { errorRes } from '@utils/errorRes';
import { logger } from '@utils/logger';
import { verifyJWT } from '@utils/verifyJWT';
Expand Down Expand Up @@ -31,7 +31,7 @@ export const onAddComment = (socket: Socket) => async (req: { cid: string, comme
if (!updated) {
return socket.emit('addCommentError', errorRes('Failed to add comment!', 'warning'));
}
return io.emit('addComment', { cid, title, comment: updated.comments.slice(-1)[0] });
return io.emit('addComment', { cid, title, comment: updated.comments[updated.comments.length - 1] });
} catch ({ message }) {
logger.error(message);
return socket.emit('addCommentError', errorRes(message, 'error'));
Expand Down
29 changes: 19 additions & 10 deletions src/actions/candidate/allocateAll.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { RequestHandler } from 'express';
import { param, validationResult } from 'express-validator';

import { verifyTitle } from './addCandidate';

import { Handler } from '@config/types';
import { CandidateRepo, RecruitmentRepo, UserRepo } from '@database/model';
import { allocateTime } from '@utils/allocateTime';
import { errorRes } from '@utils/errorRes';
import { verifyTitle } from './addCandidate';

export const allocateAll: RequestHandler = async (req, res, next) => {
interface Body {
title: string;
}

export const allocateAll: Handler<Body> = async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
Expand All @@ -19,6 +25,9 @@ export const allocateAll: RequestHandler = async (req, res, next) => {
const { title } = req.body;
const { type } = req.params;
const recruitment = (await RecruitmentRepo.query({ title }))[0];
if (!recruitment) {
return next(errorRes('Recruitment doesn\'t exist!', 'warning'));
}
if (type === 'group') {
const groupData = recruitment.groups.find(({ name }) => name === group);
if (!groupData) {
Expand All @@ -37,13 +46,13 @@ export const allocateAll: RequestHandler = async (req, res, next) => {
'interviews.group.selection.0': { $exists: true }, // selected
});
const allocations = allocateTime(groupData.interview, candidates, 'group');
const promises = allocations.map(async ({ id, time }) => {
await Promise.all(allocations.map(async ({ id, time }) => {
if (time) {
return CandidateRepo.updateById(id, { 'interviews.group.allocation': time });
}
return;
});
Promise.all(promises).then(() => res.json({ type: 'success', allocations }));
}));
res.json({ type: 'success', allocations });
} else {
if (!recruitment.interview.length) {
return next(errorRes('Please set team interview time first!', 'warning'));
Expand All @@ -57,13 +66,13 @@ export const allocateAll: RequestHandler = async (req, res, next) => {
'interviews.team.selection.0': { $exists: true }, // selected
});
const allocations = allocateTime(recruitment.interview, candidates, 'team');
const promises = allocations.map(async ({ time, id }) => {
await Promise.all(allocations.map(async ({ time, id }) => {
if (time) {
return CandidateRepo.updateById(id, { 'interviews.team.allocation': time });
}
return;
});
Promise.all(promises).then(() => res.json({ type: 'success', allocations }));
}));
res.json({ type: 'success', allocations });
}
} catch (error) {
return next(error);
Expand All @@ -72,5 +81,5 @@ export const allocateAll: RequestHandler = async (req, res, next) => {

export const allocateAllVerify = [
param('type').custom((type) => ['group', 'team'].includes(type)).withMessage('Interview type is invalid!'),
verifyTitle
verifyTitle,
];
Loading