박권수

Merge branch 'server' into web

......@@ -12,6 +12,7 @@
"@google-cloud/storage": "^5.14.2",
"@koa/cors": "^3.1.0",
"firebase-admin": "^9.11.1",
"google-auth-library": "^7.10.0",
"koa-body": "^4.2.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
......@@ -1835,9 +1836,9 @@
}
},
"node_modules/google-auth-library": {
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz",
"integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz",
"integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==",
"dependencies": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
......@@ -5277,9 +5278,9 @@
}
},
"google-auth-library": {
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz",
"integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz",
"integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==",
"requires": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
......
......@@ -20,6 +20,7 @@
"@google-cloud/storage": "^5.14.2",
"@koa/cors": "^3.1.0",
"firebase-admin": "^9.11.1",
"google-auth-library": "^7.10.0",
"koa-body": "^4.2.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
......
......@@ -93,6 +93,7 @@ exports.searchHospital = async ctx => {
};
};
//의사 회원가입
exports.doctorRegister = async ctx => {
const {
userId,
......@@ -193,6 +194,7 @@ exports.doctorRegister = async ctx => {
}
//로컬 로그인
exports.login = async(ctx) => {
const { userId, password, deviceToken } = ctx.request.body;
......@@ -211,7 +213,7 @@ exports.login = async(ctx) => {
}
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd) {
if(!user || !user.userTypeCd || user.authTypeCd !== 'NORMAL') {
ctx.status = 401;
ctx.body = {
error : '존재하지 않는 회원입니다.',
......@@ -259,6 +261,180 @@ exports.login = async(ctx) => {
};
//social Register
exports.socialRegister = async ctx => {
const { socialType } = ctx.params;
const { accessToken, deviceToken } = ctx.request.body;
const verifyingToken =
socialType.toUpperCase() === 'GOOGLE' ? async () => {
//id_token
const result = jwt.decode(accessToken);
return {
userId : result.email,
userNm : result.name,
contact : null,
birth : null,
};
}
: socialType.toUpperCase() === 'NAVER' ? async () => {
const url = 'https://openapi.naver.com/v1/nid/me';
const result = await axios.get(url, {
headers : {
Authorization : `Bearer ${accessToken}`,
},
});
const { email, mobile, name, birthday, birthyear } = result.data.response;
return {
userId : email,
userNm : name,
contact : mobile,
birth : `${birthyear}-${birthday}`,
};
}
: socialType.toUpperCase() === 'KAKAO' ? async () => {
const url = 'https://kapi.kakao.com/v2/user/me';
const result = await axios.get(url, {
headers : {
Authorization : `Bearer ${accessToken}`,
},
});
console.log(result);
return result;
} : () => null;
const verifyingInfo = await verifyingToken();
if(!verifyingInfo || !verifyingInfo.userId) {
ctx.status = 403;
ctx.body = {
error : '잘못된 요청',
};
return;
}
const { userId, userNm, birth, contact } = verifyingInfo;
const existUser = await User.findByUserId(userId);
if(existUser) {
ctx.status = 409;
ctx.body = {
error : '이미 가입된 회원',
};
return;
}
const user = new User({
userId,
hashedPassword : null,
authTypeCd : socialType.toUpperCase(),
useYn : 'Y',
});
const profile = new Profile({
userId,
userNm,
birth,
contact,
deviceToken,
});
await user.save();
await profile.save();
ctx.status = 201;
};
//social Login
exports.socialLogin = async ctx => {
const { socialType } = ctx.params;
const { accessToken, deviceToken, } = ctx.request.body;
const verifyingToken =
socialType.toUpperCase() === 'GOOGLE' ? async () => {
//id_token : google Login
const result = jwt.decode(accessToken);
return result.email;
}
: socialType.toUpperCase() === 'NAVER' ? async () => {
//naver Login
const url = 'https://openapi.naver.com/v1/nid/me';
const result = await axios.get(url, {
headers : {
Authorization : `Bearer ${accessToken}`,
},
});
return result.data.response.email;
}
: socialType.toUpperCase() === 'KAKAO' ? async () => {
//kakao Login
const url = 'https://kapi.kakao.com/v2/user/me';
const result = await axios.get(url, {
headers : {
Authorization : `Bearer ${accessToken}`,
},
});
console.log(result);
return result;
} : () => null;
const userId = await verifyingToken();
if(!userId) {
ctx.status = 403;
ctx.body = {
error : '잘못된 요청입니다',
};
return;
}
const user = await User.findByUserId(userId);
if(!user || user.useYn !== 'Y') {
ctx.status = 404;
ctx.body = {
error : '존재하지 않는 회원입니다.',
};
return;
} else if (user.authTypeCd !== socialType.toUpperCase()) {
ctx.status = 400;
ctx.body = {
error : '잘못된 소셜 로그인입니다.',
};
return;
}
const profile = await Profile.findOne({ userId });
if(profile.deviceToken !== deviceToken) {
profile.updateDeviceToken(deviceToken);
await profile.save();
}
const token = await user.generateToken();
ctx.status = 200;
ctx.body = {
userTypeCd : user.userTypeCd,
token,
};
};
exports.logout = async(ctx) => {
ctx.cookies.set('access_token', null, {
httpOnly : true,
......
......@@ -5,7 +5,7 @@ const authCtrl = require('./auth.ctrl')
const auth = new Router()
/**
* 회원가입 (email type) : 환자 회원가입
* 로컬 회원가입 (email type) : 환자 회원가입
* url : http://localhost:4000/api/auth/register
* request parameter : userId, password, passwordCheck
* return : null
......@@ -21,7 +21,7 @@ auth.post('/register', authCtrl.register)
auth.get('/hospital', authCtrl.searchHospital);
/**
* 회원가입 (email type) : 의사 회원가입
* 로컬 회원가입 (email type) : 의사 회원가입
* url : http://localhost:4000/api/auth/register/doctor
* request parameter : userId, password, passwordCheck, doctorInfo(File)
* return : null
......@@ -29,14 +29,30 @@ auth.get('/hospital', authCtrl.searchHospital);
auth.post('/register/doctor', KoaBody, authCtrl.doctorRegister)
/**
* 로그인 (email type)
* 로컬 로그인 (email type)
* url : http://localhost:4000/api/auth/login
* request parameter : userId, password
* return : userId
* return : token, userTypeCd
*/
auth.post('/login', authCtrl.login)
/**
* 소셜 회원가입(Google, Naver, Kakao)
* url : http://localhost:4000/api/auth/register/${socialType}
* request parameter : accessToken
* return : status
*/
auth.post('/register/social/:socialType', authCtrl.socialRegister);
/**
* 소셜 로그인(Google, Naver, Kakao)
* url : http://localhost:4000/api/auth/login/${socialType}
* request parameter
* return : token, userTypeCd
*/
auth.post('/login/social/:socialType', authCtrl.socialLogin);
/**
* 로그아웃
* url : http://localhost:4000/api/auth/logout
* request parameter : null
......
......@@ -15,6 +15,7 @@ const jwt = require('jsonwebtoken');
const { uploadQrCode, viewQrCode } = require('../../util/GoogleCloudStorage');
const QrCodeUtil = require('../../util/QrCodeUtil');
const { sendPushMessage } = require('../../util/FCM');
/**
......@@ -341,7 +342,19 @@ exports.writeReqBottleFeedback = async ctx => {
doctorId : userId,
feedback,
});
newFeedback.save();
await newFeedback.save();
//feedback 알람 보내기
const hub = await Hub.findOne({ hubId : bottle.hubId });
const patientProfile = await Profile.findOne({ userId : hub.userId });
if(patientProfile) {
sendPushMessage({
deviceToken : patientProfile.deviceToken,
title : '의사에게 새로운 알람이 도착했습니다.',
body : feedback,
});
}
ctx.status = 200;
......
......@@ -8,6 +8,7 @@ const jwtMiddleware = async (ctx, next) => {
}
try {
// eslint-disable-next-line no-undef
const decoded = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = {
_id : decoded._id,
......
......@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken');
const Schema = mongoose.Schema;
const UserSchema = new Schema ({
userId : { type: String, required : true, unique : true, lowercase : true, },
hashedPassword : { type : String, required : true },
userTypeCd : { type : String, required : true, default : 'NORMAL' },
userId : { type: String, required : true, unique : true, lowercase : true, trim : true },
hashedPassword : { type : String, required : true, },
userTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
authTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
useYn : { type : String, default : 'W', required : true, },
});
......
......@@ -20,20 +20,6 @@ const updateMedicineInfo = require('../lib/UpdatingMedicineInfo');
const { sendPushMessage } = require('./FCM');
// //매년 1월 1일 00시 00분에 1살씩 추가
// exports.CheckNewYear = () => {
// cron.schedule('0 0 0 1 1 *', async () => {
// const profileList = await Profile.find();
// profileList.forEach(async profile => {
// await profile.updateUserAge();
// profile.save();
// });
// }, {
// timezone : 'Asia/Tokyo',
// });
// };
//매월 1일 0시 0분에 약 정보 업데이트
exports.updateMedicineData = async () => {
cron.schedule('0 0 0 1 * *', () => {
......@@ -76,7 +62,8 @@ exports.pushNotifyByDosage = async() => {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
message : medicine.name + '을 복용하셔야 합니다.',
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
......@@ -102,7 +89,8 @@ exports.pushNotifyByDosage = async() => {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
message : medicine.name + '을 복용하셔야 합니다.',
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
......@@ -128,7 +116,8 @@ exports.pushNotifyByDosage = async() => {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
message : medicine.name + '을 복용하셔야 합니다.',
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
......@@ -139,10 +128,11 @@ exports.pushNotifyByDosage = async() => {
};
const pushNotify = ({ deviceToken, message }) => {
const pushNotify = ({ deviceToken, title, body }) => {
//toDo : deviceToken을 받아서 push notification을 발송하는 함수
sendPushMessage({
deviceToken,
message,
title,
body,
});
};
......
const BottleMedicine = require('../models/bottleMedicine');
const TakeMedicineHist = require('../models/takeMedicineHistory');
//message subscribe 후 message를 가공한 이후 해당 데이터를 보낼 topic과 message를 리턴하는 함수
exports.dataPublish = async (topic, message) => {
//client가 subscribe를 하면 메시지를 보낸 약병의 topic과 message를 가공 및 보낸 약병의 bottleId를 가져옴
......
......@@ -7,11 +7,11 @@ exports.initializeFCM = () => {
});
};
exports.sendPushMessage = async ({ deviceToken, message }) => {
exports.sendPushMessage = async ({ deviceToken, title, body }) => {
const notifyMessage = {
notification : {
title : '약 먹을 시간입니다',
body : message,
title,
body,
},
token : deviceToken,
};
......
......@@ -2133,10 +2133,17 @@
"string_decoder" "~1.1.1"
"util-deprecate" "~1.0.1"
<<<<<<< HEAD
"readable-stream@~2.3.6":
"integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw=="
"resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
"version" "2.3.7"
=======
"google-auth-library@^7.0.0", "google-auth-library@^7.0.2", "google-auth-library@^7.10.0", "google-auth-library@^7.6.1":
"integrity" "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ=="
"resolved" "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz"
"version" "7.10.0"
>>>>>>> server
dependencies:
"core-util-is" "~1.0.0"
"inherits" "~2.0.3"
......