diff --git a/.eslintrc.json b/.eslintrc.json index 9a8cffd..38b1208 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,6 +24,7 @@ "no-unused-vars": 1, "no-multiple-empty-lines": 0, "space-before-function-paren": 0, - "eol-last": 0 + "eol-last": 0, + "brace-style": ["error", "stroustrup"] } } diff --git a/.gitignore b/.gitignore index 125b84d..ea750e1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .idea package-lock.json config/config.json -test.js +fcm.js +serviceAccountKey.json diff --git a/app.js b/app.js index 0135041..c6587b0 100644 --- a/app.js +++ b/app.js @@ -5,14 +5,17 @@ const cookieParser = require('cookie-parser'); const logger = require('morgan'); const session = require('express-session'); const sequelize = require('./models').sequelize; +const util = require('./utils'); const indexRouter = require('./routes/index'); const authRouter = require('./routes/auth'); const usersRouter = require('./routes/users'); // TODO: not use yet -// const breakdownRouter = require('./routes/breakdown'); +const breakdownRouter = require('./routes/breakdown'); const eventRouter = require('./routes/event'); +const finRouter = require('./routes/fin'); + // const guestRouter = require('./routes/guest'); // const organizerRouter = require('./routes/organizer'); @@ -47,8 +50,23 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/auth', authRouter); app.use('/users', usersRouter); -// app.use('/breakdown', breakdownRouter); + +// auth check +app.use('/*', function(req, res, next) { + if(req.method === 'OPTIONS') { + return next(); + } + const result = util.getUser(req); + if(result.user === undefined) { + res.status(401).json('no auth'); + return; + } + res.locals.user = result.user; + next(); +}); app.use('/event', eventRouter); +app.use('/fin', finRouter); +app.use('/breakdown', breakdownRouter); // app.use('/guest', guestRouter); // app.use('/organizer', organizerRouter); diff --git a/config/config.json.sample b/config/config.json.sample index fb1ab4f..5ed7c5d 100644 --- a/config/config.json.sample +++ b/config/config.json.sample @@ -1,12 +1,22 @@ { - "development": { - "username": "root", - "password": "1234", - "database": "nh_db", - "host": "127.0.0.1", - "dialect": "mysql", - "operatorAliases" : false, - "logging": false, - "jwtSecret": "insert jwt secret key" + "development": { + "dbOptions": { + "username": "root", + "password": "1234", + "database": "nh_db", + "host": "127.0.0.1", + "dialect": "mysql", + "operatorAliases" : false, + "dialectOptions": { + "dateStrings": true, + "typeCast": true + }, + "timezone": "+09:00", + "logging": false + }, + "jwtSecret": "insert jwt secret key", + "iscd": "기관코드", + "accessToken": "액세스 토큰", + "BrdtBrno": "생년월일 or 사업자번호" } } diff --git a/models/breakdown.js b/models/breakdown.js new file mode 100644 index 0000000..a47af32 --- /dev/null +++ b/models/breakdown.js @@ -0,0 +1,45 @@ +module.exports = (sequelize, DataTypes) => { + return sequelize.define('breakdown', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + unique: true, + autoIncrement: true, + primaryKey: true, + }, + event_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + sender_id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + }, + sender_name: { + type: DataTypes.STRING(20), + allowNull: true, + }, + transfer_datetime: { + type: DataTypes.DATE, + allowNull: false, + }, + message: { + type: DataTypes.STRING(45), + allowNull: true, + defaultValue: null, + }, + money: { + type: DataTypes.INTEGER, + allowNull: false, + }, + is_direct_input: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + }, + { + timestamps: false, + }); +}; diff --git a/models/event.js b/models/event.js index 3d3035a..6ce3de2 100644 --- a/models/event.js +++ b/models/event.js @@ -7,6 +7,11 @@ module.exports = (sequelize, DataTypes) => { autoIncrement: true, primaryKey: true, }, + event_hash: { + type: DataTypes.STRING(100), + unique: true, + allowNull: true, + }, user_id: { type: DataTypes.INTEGER, allowNull: false, @@ -32,17 +37,18 @@ module.exports = (sequelize, DataTypes) => { allowNull: true, defaultValue: null, }, - start_datetime: { + event_datetime: { type: DataTypes.DATE, allowNull: false, }, - end_datetime: { - type: DataTypes.DATE, + is_activated: { + type: DataTypes.BOOLEAN, allowNull: false, }, - is_activated: { + is_received: { type: DataTypes.BOOLEAN, allowNull: false, + defaultValue: false, }, }, { diff --git a/models/index.js b/models/index.js index 468205b..b87f6e4 100644 --- a/models/index.js +++ b/models/index.js @@ -1,7 +1,7 @@ const path = require('path'); const Sequelize = require('sequelize'); const env = process.env.NODE_ENV || 'development'; -const config = require(path.join(__dirname, '..', 'config', 'config.json'))[env]; +const config = require(path.join(__dirname, '..', 'config', 'config.json'))[env]['dbOptions']; const db = {}; @@ -13,6 +13,11 @@ db.User = require('./user')(sequelize, Sequelize); db.Event = require('./event')(sequelize, Sequelize); db.EventAdmin = require('./eventadmin')(sequelize, Sequelize); db.Guest = require('./guest')(sequelize, Sequelize); +db.BreakDown = require('./breakdown')(sequelize, Sequelize); + +db.Event.hasMany(db.Guest, {foreignKey: {name: 'event_id'}}); +db.Event.hasMany(db.EventAdmin, {foreignKey: {name: 'event_id'}}); +db.Event.hasMany(db.BreakDown, {foreignKey: {name: 'event_id'}}); +db.User.hasMany(db.BreakDown, {foreignKey: {name: 'sender_id'}}); -db.Event.hasMany(db.Guest, { foreignKey: { name: 'event_id'} }); module.exports = db; diff --git a/models/user.js b/models/user.js index cabcbe2..4e645d7 100644 --- a/models/user.js +++ b/models/user.js @@ -30,6 +30,11 @@ module.exports = (sequelize, DataTypes) => { allowNull: true, unique: true, }, + firebase_token: { + type: DataTypes.STRING(200), + allowNull: true, + unique: true, + }, }, { timestamps: false, diff --git a/package.json b/package.json index 10ff71c..f3974f0 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,16 @@ "dev-node": "node ./bin/www" }, "dependencies": { + "axios": "^0.21.0", "bcrypt": "^5.0.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", + "crypto": "^1.0.1", "debug": "~2.6.9", "dotenv": "^8.2.0", "express": "^4.16.4", "express-session": "^1.17.1", + "firebase-admin": "^9.4.1", "http-errors": "^1.8.0", "jsonwebtoken": "^8.5.1", "morgan": "~1.9.1", diff --git a/routes/auth.js b/routes/auth.js index 8868bb6..21e022c 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -34,7 +34,8 @@ router.get('/', function(req, res, next) { const session = req.session; if(session.name) { res.json(session.name); - } else{ + } + else{ res.json('login fail'); } }); @@ -43,14 +44,15 @@ router.get('/', function(req, res, next) { router.post('/login', async function(req, res, next) { try { const {body} = req; - const user = await User.findOne({attributes: ['id', 'password'], where: {username: body.username}}); + const user = await User.findOne({attributes: ['id', 'password', 'fin_account','name'], where: {username: body.username}}); if(user === null) { console.log('cannot find user'); res.status(404).json({ 'detail': 'No user found.', }); - } else { + } + else { console.log(user.dataValues); const dbPassword = user.dataValues.password; const inputPassword = body.password; @@ -67,7 +69,8 @@ router.post('/login', async function(req, res, next) { token: jwtSignUser(user), user: user, }); - } else { + } + else { console.log('pw incorrect'); res.status(401).json({ auth: false, @@ -75,7 +78,8 @@ router.post('/login', async function(req, res, next) { }); } } - } catch (error) { + } + catch (error) { console.log('Login Error'); next(error); } diff --git a/routes/breakdown.js b/routes/breakdown.js index 935c3d3..7858844 100644 --- a/routes/breakdown.js +++ b/routes/breakdown.js @@ -1,4 +1,233 @@ const express = require('express'); const router = express.Router(); +const moment = require('moment'); -module.exports = router; \ No newline at end of file +const {BreakDown, User, Event} = require('../models'); +const util = require('../utils'); +const code = util.code; + +masterCheck = async function(userId, eventId) { + const check = await Event.findOne({ + where: {user_id: userId, id: eventId}, + }); + return check !== null; +}; + +router.get('/event/:eventId', async function(req, res, next) { + const responseJson = {}; + const eventId = req.params.eventId; + if(!await masterCheck(res.locals.user.id, eventId)) { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + res.json(responseJson); + return; + } + + try { + const result = await User.findAll( + { + include: [ + { + model: BreakDown, + where: { + event_id: eventId, + sender_name: null, + }, + attributes: ['id', 'transfer_datetime', 'message', 'money'], + }, + ], + attributes: ['name'], + }, + ); + const list = []; + + for(let i = 0; i < result.length; i++) { + const {name, breakdowns} = result[i].dataValues; + for(let j = 0; j < breakdowns.length; j++) { + const temp = {}; + temp.id = breakdowns[j].id; + temp.name = name; + temp.money = breakdowns[j].money; + temp.message = breakdowns[j].message; + temp.transfer_datetime = breakdowns[j].transfer_datetime; + temp.isDirectInput = false; + + list.push(temp); + } + } + + const result2 = await BreakDown.findAll( + { + where: { + event_id: eventId, + sender_id: 0, + }, + attributes: ['id', 'sender_name', 'transfer_datetime', 'message', 'money'], + }, + ); + + for(let i = 0; i < result2.length; i++) { + const temp = {}; + const {id, sender_name, transfer_datetime, message, money} = result2[i].dataValues; + + temp.id = id; + temp.name = sender_name; + temp.transfer_datetime = transfer_datetime; + temp.message = message; + temp.money = money; + temp.isDirectInput = true; + + list.push(temp); + } + + console.log(list); + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + responseJson.data = list; + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + } + finally { + res.json(responseJson); + } +}); + +router.get('/sender', async function(req, res, next) { + const responseJson = {}; + const userId = res.locals.user.id; + + try { + const result = await Event.findAll( + { + include: [ + { + model: BreakDown, + where: { + sender_id: userId, + }, + attributes: ['transfer_datetime', 'message', 'money'], + }, + ], + attributes: ['title'], + }, + ); + + const list = []; + + for(let i = 0; i < result.length; i++) { + const {breakdowns} = result[i].dataValues; + for(let j = 0; j < breakdowns.length; j++) { + const temp = {}; + const {transfer_datetime, message, money} = breakdowns[j]; + temp.title = result[i].dataValues.title; + temp.transfer_datetime = transfer_datetime; + temp.message = message; + temp.money = money; + + list.push(temp); + } + } + console.log(list); + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + responseJson.data = list; + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + } + finally { + res.json(responseJson); + } +}); + +router.post('/', async function(req, res, next) { + const responseJson = {}; + const {name, eventId, money} = req.body; + if(!await masterCheck(res.locals.user.id, eventId)) { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + res.json(responseJson); + return; + } + + try { + const transferTime = moment().format('YYYY-MM-DD HH:mm:ss'); + const data = { + event_id: Number(eventId), + sender_name: name, + transfer_datetime: transferTime, + money: Number(money), + is_direct_input: true, + }; + + const result = await BreakDown.create(data); + + responseJson.result = code.SUCCESS; + responseJson.detail = 'breakdown insert success'; + } + catch(exception) { + console.log(exception); + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + } + finally { + res.json(responseJson); + } +}); + +router.delete('/:id', async function(req, res, next) { + const responseJson = {}; + + try { + const result = await BreakDown.findOne( + { + where: { + id: req.params.id, + }, + attributes: ['id', 'event_id'], + }, + ); + + const eventId = result.dataValues.event_id; + + if(!await masterCheck(res.locals.user.id, eventId)) { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + res.json(responseJson); + return; + } + + const result2 = await BreakDown.destroy( + { + where: { + id: req.params.id, + is_direct_input: true, + }, + }, + ); + if(result2 === 0) { + responseJson.result = code.NO_DATA; + responseJson.detail = 'no direct input, or no data'; + } + else if(result2 === 1) { + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + } + else { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'success'; + } + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + } + finally { + res.json(responseJson); + } +}); + +module.exports = router; diff --git a/routes/event.js b/routes/event.js index 7225627..5a38bb5 100644 --- a/routes/event.js +++ b/routes/event.js @@ -1,38 +1,31 @@ const express = require('express'); const router = express.Router(); -const {User, Event, EventAdmin, Guest} = require('../models'); +const crypto = require('crypto'); +const {sequelize, User, Event, EventAdmin, Guest} = require('../models'); const util = require('../utils'); const code = util.code; masterCheck = async function(userId, eventId) { const check = await Event.findOne({ - where: {user_id: userId, id: eventId} - }); + where: {user_id: userId, id: eventId}, + }); return check !== null; -} +}; guestCheck = async function(userId, eventId) { const check = await Guest.findOne({ - where: {user_id: userId, event_id: eventId} + where: {user_id: userId, event_id: eventId}, }); return check !== null; -} - -// TO-DO : Solve Option Problem -router.options('/', function(req, res) { - return res.send({}); -}); +}; -router.all('/*', function(req, res, next) { - const result = util.getUser(req); - if(result.user === undefined) { - res.status(401).json('no auth'); - return; - } - res.locals.user = result.user; - next(); -}); +adminCheck = async function(userId, eventId) { + const check = await EventAdmin.findOne({ + where: {user_id: userId, event_id: eventId}, + }); + return check !== null; +}; // post1 : check phonenubmer router.post('/', async function(req, res, next) { @@ -48,10 +41,12 @@ router.post('/', async function(req, res, next) { if(adminResult == null) { admin.push({user_id: null, user_phone: eventAdmin[i]}); - } else { + } + else { admin.push({user_id: adminResult.id, user_phone: eventAdmin[i]}); } - } else { + } + else { responseJson.result = code.PHONE_NUMBER_INVALID; responseJson.detail = 'phone number invalid'; res.json(responseJson); @@ -62,103 +57,203 @@ router.post('/', async function(req, res, next) { next(); }); + // post2 : event insert router.post('/', async function(req, res, next) { const responseJson = {}; const body = req.body; + const myId = res.locals.user.id; + const admins = res.locals.admins; + let transaction = await sequelize.transaction(); try { const result = await Event.create( { - user_id: res.locals.user.id, + user_id: myId, category: body.category, title: body.title, location: body.location, body: body.body, invitation_url: body.invitationUrl, - start_datetime: body.startDatetime, - end_datetime: body.endDatetime, - is_activate: true, - }, + event_datetime: body.eventDatetime, + is_activated: true, + }, + { transaction } ); - console.dir(result); const eventId = result.dataValues.id; - for(let i = 0; i < res.locals.admins.length; i++) { - res.locals.admins[i].event_id = eventId; + res.locals.eventId = eventId; + res.locals.title = result.dataValues.title; + res.locals.eventDatetime = result.dataValues.event_datetime; + + for(let i = 0; i < admins.length; i++) { + admins[i].event_id = eventId; + } + const encrypt = crypto.createHash('sha256').update(eventId + ' ').digest('hex'); + const updateResult = await Event.update( + { + event_hash: encrypt, + }, + { + where: { + id: eventId, + }, + transaction + }, + ); + + //event admin insert + const result2 = await EventAdmin.bulkCreate(admins, { transaction }); + + const userAdmin = []; + const noUserAdmin = []; + + for(let i = 0; i < result2.length; i++) { + const temp = result2[i].dataValues; + if(temp.user_id === null) { + noUserAdmin.push(temp.user_phone); + } + else if(temp.user_id !== myId) { + userAdmin.push(temp.user_id); + } } + const eventAdmins = { + user: userAdmin, + noUser: noUserAdmin, + }; + + res.locals.adminIds = eventAdmins; + await transaction.commit(); next(); - } catch(exception) { + } + catch(exception) { console.log(exception); + if (transaction) { + await transaction.rollback(); + } responseJson.result = code.UNKNOWN_ERROR; - responseJson.detail = 'unknown error1'; + responseJson.detail = 'event create db error'; res.json(responseJson); } }); -// post3 : eventadmin insert +// sendFcm or sms router.post('/', async function(req, res, next) { const responseJson = {}; - const admins = res.locals.admins; - + const adminIds = res.locals.adminIds; try { - const result = await EventAdmin.bulkCreate(admins); + const result = await User.findAll( + { + where: { + id: adminIds.user, + }, + attributes: ['id', 'phone_number', 'firebase_token'], + }, + ); + + const fbTokens = []; + for(let i = 0; i < result.length; i++) { + const temp = result[i].dataValues.firebase_token; + if(temp != null) { + fbTokens.push(temp); + } + else { + adminIds.noUser.push(result[i].dataValues.phone_number); + } + } + + if(fbTokens.length !== 0) { + const fcm = await util.sendFcm( + res.locals.title, + res.locals.title + '의 관리자로 초대되었습니다.\n' + '시간 : ' + res.locals.eventDatetime + '\n', + '서버주소/event/' + res.locals.eventId, + fbTokens, + ); + } + + // TODO : admins.noUser = 회원가입 안된 사람들 문자로 responseJson.result = code.SUCCESS; responseJson.detail = 'success'; - } catch(exception) { + responseJson.data = {id: res.locals.eventId}; + } + catch(exception) { console.log(exception); responseJson.result = code.UNKNOWN_ERROR; - responseJson.detail = 'unknown error2'; - } finally { + responseJson.detail = 'unknown error3'; + } + finally { res.json(responseJson); } }); -router.put('/', async function(req, res, next) { +router.put('/:id', async function(req, res, next) { const responseJson = {}; const body = req.body; + const userId = res.locals.user.id; + const eventId = req.params.id; try { - const result = await Event.update( - { - title: body.title, - location: body.location, - body: body.body, - invitation_url: body.invitation_url, - start_datetime: body.startDatetime, - end_datetime: body.endDatetime, - }, - { - where: { - id: body.id, - }}, - ); - responseJson.result = code.SUCCESS; - responseJson.detail = 'success'; - } catch(exception) { + if(await masterCheck(userId, eventId)) { + const result = await Event.update( + { + category: body.category, + title: body.title, + location: body.location, + body: body.body, + invitation_url: body.invitationUrl, + event_datetime: body.eventDatetime, + }, + { + where: { + id: eventId, + }}, + ); + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + } + else { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + } + } + catch(exception) { responseJson.result = code.UNKNOWN_ERROR; responseJson.detail = 'unknown error'; - } finally { + } + finally { res.json(responseJson); } }); router.delete('/:id', async function(req, res, next) { const responseJson = {}; + const eventId = req.params.id; + const myId = res.locals.user.id; + let transaction = await sequelize.transaction(); try { - const result = await Event.destroy( - {where: {id: req.params.id}}, - ); - - const adminResult = await EventAdmin.destroy( - {where: {event_id: req.params.id}}, - ); - - responseJson.result = code.SUCCESS; - responseJson.detail = 'success'; - } catch(exception) { + if(await masterCheck(myId, eventId)) { + const result = await Event.destroy( + {where: {id: eventId}, transaction}, + ); + const adminResult = await EventAdmin.destroy( + {where: {event_id: eventId}, transaction}, + ); + await transaction.commit(); + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + } + else { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + } + } + catch(exception) { + if (transaction) { + await transaction.rollback(); + } responseJson.result = code.UNKNOWN_ERROR; - responseJson.detail = 'unknown error'; - } finally { + responseJson.detail = 'event delete error'; + } + finally { res.json(responseJson); } }); @@ -166,43 +261,80 @@ router.delete('/:id', async function(req, res, next) { router.get('/', async function(req, res, next) { const responseJson = {}; try { - const is_host = req.query.host; - if(typeof is_host === 'undefined') { + const isHost = req.query.host; + if(typeof isHost === 'undefined') { responseJson.result = code.INVALID_QUERY; - responseJson.detail = 'params error'; - } else if(is_host === 'true') { - const result = await Event.findAll( + responseJson.detail = 'params error'; + } + else if(isHost === 'true') { + const result1 = await Event.findAll( { where: {user_id: res.locals.user.id}, order: [ - ['is_activate', 'DESC'], - ['end_datetime', 'DESC'], + ['is_activated', 'DESC'], + ['event_datetime', 'DESC'], ], }, ); + const result2 = await Event.findAll({ + include: [{ + model: EventAdmin, + where: {user_id: res.locals.user.id}, + }], + order: [ + ['is_activated', 'DESC'], + ['event_datetime', 'DESC'], + ], + }); + const result=[]; + for(let i = 0; i < result1.length; i++) { + result.push(result1[i].dataValues); + } + for(let i = 0; i < result2.length; i++) { + result.push(result2[i].dataValues); + } + result.sort(function(a, b) { + if(a.is_activated >= b.is_activated) { + if(a.event_datetime > b.event_datetime) { + return -1; + } + else if(a.event_datetime === b.event_datetime) { + return 0; + } + else { + return 1; + } + } + else { + return 1; + } + }); responseJson.result = code.SUCCESS; responseJson.detail = 'success'; responseJson.data = result; - } else { + } + else { const result = await Event.findAll({ include: [{ model: Guest, where: {user_id: res.locals.user.id}, }], order: [ - ['is_activate', 'DESC'], - ['end_datetime', 'DESC'], + ['is_activated', 'DESC'], + ['event_datetime', 'DESC'], ], }); responseJson.result = code.SUCCESS; - responseJson.detail = 'success'; + responseJson.detail = 'success'; responseJson.data = result; } - } catch(exception) { + } + catch(exception) { console.log(exception); responseJson.result = code.UNKNOWN_ERROR; responseJson.detail = 'unknown error'; - } finally { + } + finally { res.json(responseJson); } }); @@ -219,47 +351,52 @@ router.get('/:id', async function(req, res, next) { if (result === null) { responseJson.result = code.NO_DATA; responseJson.detail = 'cannot find eventData'; - } else { - - let haveAuth = result.dataValues.user_id === myId; - if(!haveAuth) { - if(await guestCheck(myId,eventId)) { - haveAuth = true; + } + else { + const data = result.dataValues; + if(result.dataValues.user_id === myId) { + data.userType = 'master'; + } + else { + if(await adminCheck(myId, eventId)) { + data.userType = 'admin' + } + else if(await guestCheck(myId, eventId)) { + data.userType = 'guest' } } - if(haveAuth) { - const data = result.dataValues; + if(typeof data.userType !== 'undefined') { const result2 = await EventAdmin.findAll( { attributes: ['user_phone'], - where: {event_id: eventId} + where: {event_id: eventId}, }, ); data.eventAdmin = result2; responseJson.result = code.SUCCESS; - responseJson.detail = 'success'; + responseJson.detail = 'success'; responseJson.data = data; } else { responseJson.result = code.NO_AUTH; - responseJson.detail = 'no_auth' + responseJson.detail = 'no_auth'; } } - } catch(exception) { + } + catch(exception) { responseJson.result = code.UNKNOWN_ERROR; responseJson.detail = 'unknown error'; console.log(exception); - } finally { + } + finally { res.json(responseJson); } }); - - // event close router.put('/close/:id', async function(req, res, next) { const responseJson = {}; - const eventId = req.params.id; + const eventId = req.params.id; const myId = res.locals.user.id; try { if(await masterCheck(myId, eventId)) { @@ -275,15 +412,79 @@ router.put('/close/:id', async function(req, res, next) { ); responseJson.result = code.SUCCESS; responseJson.detail = 'success'; - } else { + } + else { responseJson.result = code.NO_AUTH; - responseJson.detail = 'no_auth' + responseJson.detail = 'no_auth'; } - } catch(exception) { + } + catch(exception) { console.log(exception); responseJson.result = code.UNKNOWN_ERROR; responseJson.detail = 'unknown error'; - } finally { + } + finally { + res.json(responseJson); + } +}); + +router.get('/invite/:hash', async function(req, res, next) { + const responseJson = {}; + const myId = res.locals.user.id; + try { + const hostId = req.query.hostId; + const hash = req.params.hash; + + const result = await Event.findOne({ + attributes: ['id', 'user_id'], + where: {event_hash: hash}, + }); + + if (result === null) { + responseJson.result = code.NO_DATA; + responseJson.detail = 'cannot find event_hash'; + } + else { + const eventId = result.dataValues.id; + const hostCheck = result.dataValues.user_id == hostId || await adminCheck(hostId, eventId); + if(hostCheck) { + const data = {event_id: eventId}; + if(result.dataValues.user_id == myId) { + data.userType = 'master'; + } + else { + if(await adminCheck(myId, eventId)) { + data.userType = 'admin' + } + else { + data.userType = 'guest' + } + } + if(data.userType === 'guest') { + const result2 = await Guest.findOrCreate({ + where:{ + user_id: myId, + event_id: eventId, + eventAdmin_id: hostId, + } + }); + } + responseJson.result = code.SUCCESS; + responseJson.detail = 'success'; + responseJson.data = data; + } + else { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no_auth : hostId is not valid'; + } + } + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + console.log(exception); + } + finally { res.json(responseJson); } }); diff --git a/routes/fin.js b/routes/fin.js new file mode 100644 index 0000000..bcb5726 --- /dev/null +++ b/routes/fin.js @@ -0,0 +1,358 @@ +const express = require('express'); +const router = express.Router(); +const path = require('path'); +const env = process.env.NODE_ENV || 'development'; +const config = require(path.join(__dirname, '..', 'config', 'config.json'))[env]; +const moment = require('moment'); +const axios = require('axios'); +const sequelize = require('sequelize'); + +const {User, Event, BreakDown} = require('../models'); +const util = require('../utils'); +const code = util.code; + + +createBodyHeader = function(apiNm, userId) { + const time = moment().format('YYYYMMDD-HHmmss'); + const tsymd = time.substring(0, 8); + const trtm = time.substring(9, 15); + const ut = Math.floor(new Date().getTime()); + const isTuno = ut + userId.toString(); + + const postBodyHeader = { + 'Iscd': config.iscd, + 'FintechApsno': '001', // 테스트용 고정 + 'ApiSvcCd': 'DrawingTransferA', // 테스트용 고정 + 'AccessToken': config.accessToken, + }; + + postBodyHeader.ApiNm = apiNm; // API 명 + postBodyHeader.Tsymd= tsymd; // 전송일자 + postBodyHeader.Trtm =trtm; // 전송시각 + postBodyHeader.IsTuno=isTuno; // 기관거래고유번호 + return postBodyHeader; +}; + +getNHURL = function(apiNm) { + return 'https://developers.nonghyup.com/' + apiNm + '.nh'; +}; + +// NH API OpenFinAccountDirect +router.post('/', async function(req, res, next) { + const responseJson= {}; + try { + const userId = res.locals.user.id; + const apiNm = 'OpenFinAccountDirect'; + const body= { + 'DrtrRgyn': 'Y', + 'BrdtBrno': config.BrdtBrno, + 'Bncd': req.body.bncd, + 'Acno': req.body.acno, + }; + body.Header= createBodyHeader(apiNm, userId); + const result = await axios.post(getNHURL(apiNm), body); + if(result.data.Header.Rpcd !== '00000') { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = result.data.Header.Rsms; + res.json(responseJson); + } + else { + res.locals.Rgno=result.data.Rgno; + next(); + } + } + catch (error) { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = 'nh api( OpenFinAccountDirect ) error'; + res.json(responseJson); + } +}); +// NH API CheckOpenFinAccountDirect +router.post('/', async function(req, res, next) { + const responseJson= {}; + try { + const userId = res.locals.user.id; + const rgno = res.locals.Rgno; + + const apiNm = 'CheckOpenFinAccountDirect'; + const body = { + 'Rgno': rgno, + 'BrdtBrno': config.BrdtBrno, + }; + body.Header= createBodyHeader(apiNm, userId); + const result = await axios.post(getNHURL(apiNm), body); + if(result.data.Header.Rpcd !== '00000') { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = result.data.Header.Rsms; + res.json(responseJson); + } + else { + res.locals.FinAcno = result.data.FinAcno; + next(); + } + } + catch (error) { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = 'nh api (CheckOpenFinAccountDirect) error'; + res.json(responseJson); + } +}); + +// user table update +router.post('/', async function(req, res, next) { + const responseJson= {}; + try { + const userId = res.locals.user.id; + const finAcno = res.locals.FinAcno; + + const result = await User.update( + { + fin_account: finAcno, + }, + { + where: { + id: userId, + }, + }); + responseJson.result= code.SUCCESS; + responseJson.detail= 'finaccount create success'; + responseJson.data = { + fin_account: finAcno + } + res.json(responseJson); + } + catch (error) { + responseJson.result = code.DB_ERROR; + responseJson.detail = 'user table update error'; + res.json(responseJson); + } +}); + + +router.post('/transfer', async function(req, res, next) { + const responseJson= {}; + const {event_hash} = req.body; + try { + // get event id from db + const result = await Event.findOne( + {attributes: ['id'], where: {event_hash: event_hash}}, + ); + + if(result === null) { + responseJson.result = code.NO_DATA; + responseJson.detail = 'not valid event hash'; + res.json(responseJson); + return; + } + res.locals.eventId = result.dataValues.id; + // get finAcno + const userId = res.locals.user.id; + const result2 = await User.findOne( + {attributes: ['fin_account'], where: {id: userId}}, + ); + + if(result2 === null) { + responseJson.result = code.NO_DATA; + responseJson.detail = 'not register fin account'; + res.json(responseJson); + return; + } + res.locals.finAccount = result2.dataValues.fin_account; + next(); + } + catch (error) { + responseJson.result = code.DB_ERROR; + responseJson.detail = 'transfer DB find error'; + res.json(responseJson); + } +}); +// use nh api DrawingTransfer +router.post('/transfer', async function(req, res, next) { + const responseJson= {}; + const {tram} = req.body; + try { + const userId = res.locals.user.id; + const apiNm = 'DrawingTransfer'; + const body= { + 'FinAcno': res.locals.finAccount, + 'Tram': tram, + 'DractOtlt': '부조금 출금', + }; + + body.Header= createBodyHeader(apiNm, userId); + const result = await axios.post(getNHURL(apiNm), body); + if(result.data.Header.Rpcd !== '00000') { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = result.data.Header.Rsms; + res.json(responseJson); + } + else { + const time = result.data.Header.Tsymd + result.data.Header.Trtm; + res.locals.transferTime = time.replace( + /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/, + '$1-$2-$3 $4:$5:$6'); + next(); + } + } + catch (error) { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = 'nh api( DrawingTransfer ) error'; + res.json(responseJson); + } +}); +// Breakdown db insert +router.post('/transfer', async function(req, res, next) { + const responseJson= {}; + let {tram, message} = req.body; + const userId = res.locals.user.id; + const transferTime= res.locals.transferTime; + const eventId = res.locals.eventId; + // message error avoid + if(message.length > 45) { + message = message.substring(0, 45); + } + const data = { + event_id: eventId, + sender_id: userId, + transfer_datetime: transferTime, + message: message, + money: tram, + is_direct_input: false, + }; + + try { + const result = await BreakDown.create(data); + responseJson.result = code.SUCCESS; + responseJson.detail = 'transfer success'; + responseJson.data = {event_id: eventId}; + res.json(responseJson); + } + catch (error) { + responseJson.result = code.DB_ERROR; + responseJson.detail = 'transfer db insert error'; + responseJson.data = data; + res.json(responseJson); + } +}); + +// check event hash +router.post('/receive', async function(req, res, next) { + const responseJson = {}; + const {event_hash} = req.body; + + try { + const result = await Event.findOne( + { + where: { + event_hash: event_hash, + }, + }, + ); + + if(result == null) { + responseJson.result = code.NO_DATA; + responseJson.detail = 'not valid event hash'; + res.json(responseJson); + return; + } + + console.log(result); + if(result.dataValues.is_received) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'already received'; + res.json(responseJson); + return; + } + + if(result.dataValues.user_id !== res.locals.user.id) { + responseJson.result = code.NO_AUTH; + responseJson.detail = 'no auth'; + res.json(responseJson); + return; + } + + res.locals.eventId = result.dataValues.id; + res.locals.title = result.dataValues.title; + next(); + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'Unknown Error'; + res.json(responseJson); + } +}); + +// nh api +router.post('/receive', async function(req, res, next) { + const responseJson = {}; + const {bncd, acno} = req.body; + + try { + const result = await BreakDown.findAll( + { + attributes: [ + 'event_id', + [sequelize.fn('sum', sequelize.col('money')), 'totalMoney'], + ], + group: ['event_id'], + }, + { + where: { + event_id: res.locals.eventId, + is_direct_input: false, + }, + }, + ); + const tram = result[0].dataValues.totalMoney; + + const apiNm = 'ReceivedTransferAccountNumber'; + const body = { + 'Bncd': bncd, + 'Acno': acno, + 'Tram': tram, + 'DractOtlt': res.locals.title + ' 정산금', + 'MractOtlt': res.locals.title + ' 정산금', + }; + + body.Header = createBodyHeader(apiNm, res.locals.user.id); + + const nhResult = await axios.post(getNHURL(apiNm), body); + + next(); + } + catch(exception) { + responseJson.result = code.NH_API_ERROR; + responseJson.detail = 'nh api( ReceivedTransferAccountNumber ) error'; + res.json(responseJson); + } +}); + +// update event's is_received +router.post('/receive', async function(req, res, next) { + const responseJson = {}; + + try { + const result = await Event.update( + { + is_received: true, + }, + { + where: { + id: res.locals.eventId, + }, + }, + ); + + responseJson.result = code.SUCCESS; + responseJson.detail = 'receive success'; + } + catch(exception) { + responseJson.result = code.DB_ERROR; + responseJson.detail = 'db update error'; + } + finally { + res.json(responseJson); + } +}); + +module.exports = router; diff --git a/routes/users.js b/routes/users.js index 4279db6..e497f97 100644 --- a/routes/users.js +++ b/routes/users.js @@ -1,21 +1,20 @@ const express = require('express'); const router = express.Router(); -const {User} = require('../models'); +const {sequelize, User, EventAdmin} = require('../models'); const bcrypt = require('bcrypt'); const util = require('../utils.js'); -const saltRounds = 10; const code = util.code; -const responseJson = {}; - /* GET users listing. */ // 핸드폰번호 중복체크 // 회원인지 확인하고 문자 아니면 알림 보낼때 사용 router.get('/overlap/phone', async function(req, res, next) { + const responseJson = {}; if(!util.phoneNumberCheck(req.query.phone_number)) { responseJson.result = code.PHONE_NUMBER_INVALID; responseJson.detail = 'phone number invalid'; - } else { + } + else { const result = await User.findOne( {where: {phone_number: req.query.phone_number}}, ); @@ -23,7 +22,8 @@ router.get('/overlap/phone', async function(req, res, next) { if(result == null) { responseJson.result = code.SUCCESS; responseJson.detail = 'no exist'; - } else { + } + else { responseJson.result = code.PHONE_NUMBER_ALREADY_EXIST; responseJson.detail = 'phone number already exist'; } @@ -33,6 +33,7 @@ router.get('/overlap/phone', async function(req, res, next) { // 아이디 중복체크 router.get('/overlap/username', async function(req, res, next) { + const responseJson = {}; const result = await User.findOne( {where: {username: req.query.username}}, ); @@ -40,7 +41,8 @@ router.get('/overlap/username', async function(req, res, next) { if(result == null) { responseJson.result = code.SUCCESS; responseJson.detail = 'no exist'; - } else { + } + else { responseJson.result = code.USERNAME_ALREADY_EXIST; responseJson.detail = 'username already exist'; } @@ -49,24 +51,82 @@ router.get('/overlap/username', async function(req, res, next) { // 회원가입 router.post('/signup', async function(req, res, next) { + const responseJson = {}; + let transaction = await sequelize.transaction(); try { if(util.phoneNumberCheck(req.body.phone_number)) { + const bcPw = bcrypt.hashSync(req.body.password, util.saltRounds); const result = await User.create({ username: req.body.username, - password: bcrypt.hashSync(req.body.password, saltRounds), + password: bcPw, name: req.body.name, phone_number: req.body.phone_number, - }); + }, {transaction}); responseJson.result = code.SUCCESS; responseJson.detail = 'signup success'; - } else { + + const userId = result.dataValues.id; + const isInvited = await EventAdmin.findOne( + {where: {user_phone: req.body.phone_number}}, + ); + + if(isInvited) { + await EventAdmin.update( + { + user_id: userId, + }, + { + where: { + user_phone: req.body.phone_number, + }, + transaction + } + ); + } + await transaction.commit(); + } + else { responseJson.result = code.PHONE_NUMBER_INVALID; responseJson.detail = 'phone number invalid'; } - } catch(exception) { + } + catch(exception) { + if (transaction) { + await transaction.rollback(); + } responseJson.result = code.UNKNOWN_ERROR; - responseJson.detail = exception.errors[0].message; - } finally { + responseJson.detail = 'sign up error'; + } + finally { + res.json(responseJson); + } +}); + +router.put('/:id/token', async function(req, res, next) { + const responseJson = {}; + try { + const token = req.body.token; + console.dir(req.body); + const result = User.update( + { + firebase_token: token, + }, + { + where: { + id: req.params.id, + }, + }, + ); + + responseJson.result = code.SUCCESS; + responseJson.detail = 'update firebase token success'; + responseJson.data = result; + } + catch(exception) { + responseJson.result = code.UNKNOWN_ERROR; + responseJson.detail = 'unknown error'; + } + finally { res.json(responseJson); } }); diff --git a/serviceAccountKey.json.sample b/serviceAccountKey.json.sample new file mode 100644 index 0000000..7a52732 --- /dev/null +++ b/serviceAccountKey.json.sample @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "insert project_id", + "private_key_id": "insert private key id", + "private_key": "insert private key", + "client_email": "insert client email", + "client_id": "insert clien id", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "insert client x509 cert url" +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..3fe106f --- /dev/null +++ b/test.js @@ -0,0 +1,190 @@ +const request = require('request'); + +// 기관 고유 번호 +const iscd = '000504'; +const accessToken = '206782edebcb74e51aeb09292b444aa5926e87bcdc2220008e53209d84be0fab'; +// 농협 모계좌 +const nhAccount1 = '3020000002090'; +// 농협 자계좌 +const nhAccount2 = '3020000002092'; +// 상호 금융 계 +const shAccount1 = '3510000002091'; const shRgno1 = '20201130000000520'; +const shAccount2 = '3510000002093'; +const baseURL = 'https://developers.nonghyup.com/'; +const today = '20201130'; // 이건 무조건 오늘 날짜로 해야됨 +const birthday = '19960605'; +// 은행코드 011 농협, 012 농협상호금융 +const bncd = '012'; +// 등록 번호(이것이 있어야 핀어카운트 확인 가능) +const rgno = '20201128000000508'; +const rgno2 = '20201130000000519'; +// 기관 거래 고유 번호, 할 때마다 1씩 증가 시켜줘야함. +const isTuno = '0000023'; // timestamp + user_id +const finAcno = '00820100005040000000000000929'; const shFinAcno = '00820100005040000000000000992'; +const finAcno2 = '00820100005040000000000000983'; + +// request module 기본 요청 방식 +const options = { + url: baseURL, + qs: null, // for GET + body: null, // for POST + json: true, // json으로 보낼경우 true로 해주어야 header값이 json으로 설정된다. +}; + +// post api 공통부분 +const postBody = { + 'ApiNm': '', + 'Tsymd': today, // 오늘 날짜 + 'Trtm': '164011', // 요청 시간 + 'Iscd': iscd, + 'FintechApsno': '001', // 테스트용은 다 001 + 'ApiSvcCd': 'DrawingTransferA', + 'IsTuno': isTuno, + 'AccessToken': accessToken, +}; + +function makeOptions(apiNm, input) { + const ret = options; + ret.url = baseURL + apiNm + '.nh'; + ret.body = input; + ret.body.Header = postBody; + ret.body.Header.ApiNm = apiNm; + ret.url = baseURL + apiNm + '.nh'; + + return ret; +} + +// post request +function pr(opt) { + request.post(opt, function (err, httpResponse, body) { + if (err) { + console.log('ERROR\n', err); + return; + } + console.log(body); + }); +} + +// 핀어카운트 직접발급(핀어카운트 발급만 시키기, 보여주진 않음) +function openFinAccountDirect() { + const apiNm = 'OpenFinAccountDirect'; + const body = { + 'DrtrRgyn': 'Y', + 'BrdtBrno': birthday, + 'Bncd': bncd, + 'Acno': shAccount1, + }; + const ofadOpts = makeOptions(apiNm, body); + + // console.log(ofadOpts); + + pr(ofadOpts); + + // response + // Rgno : 등록번호, 이것을 이용해 핀어카운트를 확인할 수 있다. +} + +// 핀어카운트 직접발급 확인(핀어카운트 확인하기) +function checkOpenFinAccountDirect() { + const apiNm = 'CheckOpenFinAccountDirect'; + const body = { + 'Rgno': shRgno1, + 'BrdtBrno': birthday, + }; + const cofadOpts = makeOptions(apiNm, body); + + // console.log(cofadOpts); + + pr(cofadOpts); + + // response + // FinAcno : 핀어카운트 번호 + // RgsnYmd : 등록 일자 YYYYMMDD +} + +// 예금주 조회 +function inquireDepositorAccountNumber() { + const apiNm = 'InquireDepositorAccountNumber'; + const body = { + 'Bncd': bncd, + 'Acno': nhAccount1, + }; + const idanOpts = makeOptions(apiNm, body); + + console.log(idanOpts); + + pr(idanOpts); + + // response + // Bncd : 은행 코드 + // Acno : 계좌번호 + // Dpnm : 예금주명의 +} + +// 잔액 조회 +function inquireBalance() { + const apiNm = 'InquireBalance'; + const body = { + 'FinAcno': finAcno, + }; + const balanceOpts = makeOptions(apiNm, body); + + pr(balanceOpts); + + // 원장 잔액과 실지급가능액의 차이점 + // https://ko.bccrwp.org/compare/difference-between-ledger-balance-and-available-balance/ + // response + // FinAcno + // Ldbl : 원장 잔액 + // RlpmAbmant : 실 지급 가능액 + // 이하 AI001, AI002 오류 발생시 출력 + // AthrCnfrTckt : 권한 확인 티켓 + // Webrl : 웹 url + // AndInitUrl, AndAppUrl, AndWebUrl : 안드로이드 설치, 앱, 웹 url + // IosInitUrl, IosAppUrl, IosWebUrl : 아이오에스 설치, 앱, 웹 url +} + +// 출금이체 +function drawingTransfer() { + const apiNm = 'DrawingTransfer'; + const body = { + 'FinAcno': shFinAcno, + 'Tram': '100000000', + 'DractOtlt': '출금계좌에 적는 내용', + }; + const transferOpts = makeOptions(apiNm, body); + + pr(transferOpts); + + // response + // FinAcno : 핀어카운트 -> 문서에는 있는데 리턴은 안됨 + // RgsnYmd : 등록일자 +} + +// 입금이체 +function receivedTransferAccountNumber() { + const apiNm = 'ReceivedTransferAccountNumber'; + const body = { + 'Bncd': bncd, + 'Acno': nhAccount2, + 'Tram': '100000000', + 'DractOtlt': '출금계좌에 적는 내용', + 'MractOtlt': '입금계좌에 적는 내용', + }; + const receivedOpts = makeOptions(apiNm, body); + + pr(receivedOpts); + + // response + // 인자 없음 +} + +// test function +// openFinAccountDirect(); +// checkOpenFinAccountDirect(); +// inquireDepositorAccountNumber(); +// inquireBalance(); +// drawingTransfer(); +// receivedTransferAccountNumber(); + +// process : 하객 - 출금이체 - 정컴텍 - 예금주확인 - 입금 - 신랑신부 \ No newline at end of file diff --git a/utils.js b/utils.js index 78299f1..445812e 100644 --- a/utils.js +++ b/utils.js @@ -2,8 +2,16 @@ const path = require('path'); const env = process.env.NODE_ENV || 'development'; const config = require(path.join(__dirname, '.', 'config', 'config.json'))[env]; const jwt = require('jsonwebtoken'); +const admin = require('firebase-admin'); +const serviceAccount = require('./serviceAccountKey'); const value = {}; + +admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://kkotgil-bdb2e.firebaseio.com', +}); + value.code = { 'UNKNOWN_ERROR': -1, 'SUCCESS': 0, @@ -16,7 +24,9 @@ value.code = { 'NAME_INVALID': 7, 'NO_DATA': 8, 'INVALID_QUERY': 9, - 'NO_AUTH': 10 + 'NO_AUTH': 10, + 'NH_API_ERROR': 11, + 'DB_ERROR': 12, }; value.phoneNumberCheck = function (phone) { @@ -33,7 +43,8 @@ value.getUser = function (req) { let decoded = ''; try { decoded = jwt.verify(authorization, config.jwtSecret); - } catch (e) { + } + catch (e) { return {detail: 'unauthorized'}; } return {detail: 'success', user: decoded.data}; @@ -42,4 +53,33 @@ value.getUser = function (req) { return {detail: 'no header'}; }; +value.saltRounds = 10; + +value.sendFcm = async function(title, body, link, fbTokenList) { + const fcmMessage = { + data: { + title: title, + body: body, + link: link, + }, + tokens: fbTokenList, + }; + try { + const result = await admin.messaging().sendMulticast(fcmMessage); + + for(let i = 0; i < result.responses.length; i++) { + if(!result.responses[i].success) { + console.log(result.responses[i].error); + } + } + console.log('Send FCM Success'); + + return value.code.SUCCESS; + } + catch(exception) { + console.log(exception); + return value.code.UNKNOWN_ERROR; + } +}; + module.exports = value;