송용우

Merge commit '02ae3279' into feature/database

SERVER_PORT=4000
MONGO_URL=mongodb://localhost:27017/jaksimsamil
\ No newline at end of file
{
"parser": "babel-eslint",
"env": {
"commonjs": true,
"es6": true,
......@@ -12,6 +13,5 @@
"parserOptions": {
"ecmaVersion": 11
},
"rules": {
}
"rules": {}
}
......
......@@ -21,14 +21,17 @@
| group | description | method | URL | Detail | Auth |
| ------- | ------------------------ | ------ | -------------------------- | -------- | --------- |
| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
| user | 유저 삭제 | DEELTE | api/user:id | 바로가기 | JWT Token |
| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
| profile | 유저가 푼 문제 조회 | GET | api/profile/solved:id | 바로가기 | None |
| profile | 유저가 푼 문제 동기화 | Update | api/profile/solved:id | 바로가기 | None |
| profile | 유저가 푼 문제 개수 조회 | GET | api/profile/solvednum:id | 바로가기 | None |
| profile | 추천 문제 조회 | GET | api/profile/recommendps:id | 바로가기 | None |
| notify | 슬랙 메시지 전송 요청 | POST | api/notify/slack | 바로가기 | Jwt Token |
| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
| auth | 로그아웃 | GET | api/auth/logout | 바로가기 | JWT Token |
| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
......
const express = require("express");
const morgan = require("morgan");
const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const mongoose = require("mongoose");
const app = express();
const jwtMiddleware = require("./src/lib/jwtMiddleware");
const api = require("./src/api");
require("dotenv").config();
const app = new Koa();
const router = new Router();
app.use(bodyParser());
app.use(jwtMiddleware);
const { SERVER_PORT, MONGO_URL } = process.env;
app.use(
morgan("[:date[iso]] :method :status :url :response-time(ms) :user-agent")
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/api", require("./api"));
router.use("/api", api.routes());
app.use(router.routes()).use(router.allowedMethods());
mongoose
.connect(MONGO_URL, { useNewUrlParser: true, useFindAndModify: false })
.connect(MONGO_URL, {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to MongoDB");
})
.catch((e) => {
console.log(e);
});
app.listen(SERVER_PORT, () => {
console.log("Server is running on port", process.env.SERVER_PORT);
});
......
This diff could not be displayed because it is too large.
......@@ -4,15 +4,27 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"bcrypt": "^4.0.1",
"body-parser": "^1.19.0",
"cheerio": "^1.0.0-rc.3",
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0",
"eslint-config-prettier": "^6.11.0",
"express": "^4.17.1",
"fs": "^0.0.1-security",
"iconv": "^3.0.0",
"joi": "^14.3.1",
"jsonwebtoken": "^8.5.1",
"koa": "^2.12.0",
"koa-bodyparser": "^4.3.0",
"koa-router": "^9.0.1",
"mongoose": "^5.9.17",
"morgan": "^1.10.0",
"path": "^0.12.7"
"path": "^0.12.7",
"voca": "^1.4.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.1.0",
"nodemon": "^2.0.4"
},
......
const Joi = require("joi");
const User = require("../../models/user");
/*
POST /api/auth/register
{
username: 'userid'
password: 'userpassword'
}
*/
exports.register = async (ctx) => {
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(3).max(20).required(),
password: Joi.string().required(),
});
const result = Joi.validate(ctx.request.body, schema);
if (result.error) {
ctx.status = 400;
ctx.body = result.error;
return;
}
const { username, password } = ctx.request.body;
try {
const isNameExist = await User.findByUsername(username);
if (isNameExist) {
ctx.status = 409;
return;
}
const user = new User({
username,
});
await user.setPassword(password);
await user.save();
ctx.body = user.serialize();
const token = user.generateToken();
ctx.cookies.set("acces_token", token, {
//3일동안 유효
maxAge: 1000 * 60 * 60 * 24 * 3,
httpOnly: true,
});
} catch (e) {
ctx.throw(500, e);
}
};
/*
POST /api/auth/login
{
username: 'userid'
password: 'userpassword'
}
*/
exports.login = async (ctx) => {
const { username, password } = ctx.request.body;
if (!username || !password) {
ctx.status = 401;
return;
}
try {
const user = await User.findByUsername(username);
if (!user) {
ctx.status = 401;
return;
}
const isPasswordValid = await user.checkPassword(password);
if (!isPasswordValid) {
ctx.status = 401;
return;
}
ctx.body = user.serialize();
const token = user.generateToken();
ctx.cookies.set("acces_token", token, {
//7일동안 유효
maxAge: 1000 * 60 * 60 * 24 * 7,
httpOnly: true,
});
} catch (e) {
ctx.throw(500, e);
}
};
/*
GET api/auth/check
*/
exports.check = async (ctx) => {
const { user } = ctx.state;
if (!user) {
ctx.status = 401;
return;
}
ctx.body = user;
};
/*
POST /api/auth/logout
*/
exports.logout = async (ctx) => {
ctx.cookies.set("access_token");
ctx.status = 204;
};
const Router = require("koa-router");
const auth = new Router();
const authCtrl = require("./auth.ctrl");
auth.post("/login", authCtrl.login);
auth.get("/logout", authCtrl.logout);
auth.post("/register", authCtrl.register);
module.exports = auth;
const Router = require("koa-router");
const friend = new Router();
friend.post("/");
friend.delete("/:id");
friend.get("/:id");
friend.get("");
module.exports = friend;
const Router = require("koa-router");
const api = new Router();
const auth = require("./auth");
const friend = require("./friend");
const notify = require("./profile");
const user = require("./user");
const profile = require("./profile");
api.use("/auth", auth.routes());
api.use("/friend", friend.routes());
api.use("/notify", notify.routes());
api.use("/user", user.routes());
api.use("/profile", profile.routes());
module.exports = api;
......
const Router = require("koa-router");
const notify = new Router();
notify.post("/slack");
module.exports = notify;
const Router = require("koa-router");
const profile = new Router();
profile.post("/solved:id");
profile.get("/solvednum:id");
profile.get("recommendps:id");
module.exports = profile;
const Router = require("koa-router");
const user = new Router();
user.post("/");
user.delete("/:id");
user.get("/:id");
user.get("");
module.exports = user;
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const jwtMiddleware = async (ctx, next) => {
const token = ctx.cookies.get("access_token");
if (!token) {
//토큰이 없을 때
return next();
}
try {
const decoded = jwt.verify(token, process.env.JWT_TOKEN);
ctx.state.user = {
_id: decoded._id,
username: decoded.username,
};
//토큰의 남은 유효 기간이 2일 이하라면 재발급
if (decoded.exp - Date.now() / 1000 < 60 * 60 * 24 * 2) {
const user = await User.findById(decoded._id);
const token = user.generateToken();
ctx.cookies.set("access_token", token, {
maxAge: 1000 * 60 * 60 * 24 * 7,
httpOnly: true,
});
}
return next();
} catch (e) {
return next();
}
};
module.exports = jwtMiddleware;
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
hashedPassword: String,
});
UserSchema.methods.setPassword = async function (password) {
const hash = await bcrypt.hash(password, 10);
this.hashedPassword = hash;
};
UserSchema.methods.checkPassword = async function (password) {
const result = await bcrypt.compare(password, this.hashedPassword);
return result;
};
UserSchema.statics.findByUsername = function (username) {
return this.findOne({ username });
};
UserSchema.methods.serialize = function () {
const data = this.toJSON();
delete data.hashedPassword;
return data;
};
UserSchema.methods.generateToken = function () {
const token = jwt.sign(
{
_id: this.id,
username: this.username,
},
process.env.JWT_SECRET,
{
expiresIn: "7d",
}
);
return token;
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
exports.StringToDate_BJ = function (date_str) {
let arr_date = date_str.split(" "); //yyyy m dd tt MM SS Fomat LIST
let arr_date_r = arr_date.map(function (str) {
let str_r = str.slice(0, -1);
return str_r.length == 1 ? "0" + str_r : str_r;
});
return arr_date_r[0] + arr_date_r[1] + arr_date_r[2]; //YYYYMMDD 형식으로 반환
};
const axios = require("axios");
const cheerio = require("cheerio");
const StringToDate = require("./StringToDate");
/*
ToDO
- 유저 네임 검증
- 예외 처리
*/
exports.getBJ = async function (userid) {
let data_list = [];
let next_page_link = "";
await getStartPage(userid).then((html) => {
//시작 페이지를 가져온다.
//같은 객체를 두번 선언한다. 퍼포먼스에 문제 생길수도
//함수에 객체를 넘기는 방법도 있다.
//첫 페이지 가져온다.
data_list.push(getData(html));
next_page_link = getNextPageLink(html);
});
while (next_page_link != -1) {
//다음 페이지를 가져온다.
await getNextPage(next_page_link).then((html) => {
data_list.push(getData(html));
next_page_link = getNextPageLink(html);
});
}
return data_list.flat(1);
};
const getStartPage = async (userid) => {
//유저 아이디 입력
try {
return await axios.get(
"https://www.acmicpc.net/status?user_id=" + userid + "&result_id=4"
);
} catch (error) {
console.log(error);
}
};
const getNextPage = async (link) => {
//링크 입력
try {
return await axios.get(link);
} catch (error) {
console.log(error);
}
};
const getData = (html) => {
//페이지 데이터 파싱
let psArr = [];
const $ = cheerio.load(html.data);
const $bodyList = $("#status-table > tbody");
$bodyList.children().each((index, element) => {
psArr.push({
problem_number: $(element).find("a.problem_title").text(),
problem_title: $(element).find("a.problem_title").attr("title"),
solved_date: StringToDate.StringToDate_BJ(
$(element).find("a.real-time-update").attr("title")
),
});
});
return psArr;
};
const getNextPageLink = (html) => {
//다음 페이지가 있으면 다음 페이지 주소 return, 없으면 -1 return
const $ = cheerio.load(html.data);
return $("#next_page").attr("href")
? "https://www.acmicpc.net/" + $("#next_page").attr("href")
: -1;
};
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.