diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1dcef2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env \ No newline at end of file diff --git a/README.md b/README.md index 7e08b13..e265d60 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# fancy-todo \ No newline at end of file +# fancy-todo +Dokumentasi +https://documenter.getpostman.com/view/8977804/SW14Tw3m \ No newline at end of file diff --git a/client/Project.js b/client/Project.js new file mode 100644 index 0000000..e75c13e --- /dev/null +++ b/client/Project.js @@ -0,0 +1,481 @@ +function generateProjectList() { + $('#all-content').empty() + swal.fire({ + imageUrl:"https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-18.gif", + text:'Loading for Update...', + imageWidth: 200, + imageHeight: 200, + showConfirmButton: false + }) + $.ajax({ + url: HOST_SERVER + '/Project', + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + for (let i = 0; i < result.result.length; i++) { + cardProject(result.result[i]) + } + }) + .fail (err => { + swal.fire({ + type: 'error', + title: 'Terjadi kesalahan dengan server' + }) + }) + .always (() => { + swal.close() + }) +} + +function cardProject(result) { + $('#all-content').prepend(` +
+
${result.name}
+
+

Total Member: ${result.MemberId.length}

+
+

Total ToDo: ${result.ToDoId.length}

+
+

Owner: ${result.OwnerId.name}

+
+
+ +
+
+
+`) +} + +function showListToDoProject(data) { + $(`#Project-content${data}`).empty() + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/${data}`, + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + for (let i = 0; i < result.ToDoId.length; i++) { + cardToDoProject(result.ToDoId[i], data) + } + }) + .fail (err => { + + }) + .always (() => { + + }) +} + +function cardToDoProject(result, id) { + $(`#Project-content${id}`).prepend(` +
+
+ ${result.title} +
+
+
${result.description}
+
+

Due Date : ${result.dueDate.slice(0, 10)}

+
+
+ +
+
+ `) + if (result.status == false) { + $(`#ListToDo${result._id}`).css('background-color', '#ff0000') + } else { + $(`#ListToDo${result._id}`).css('background-color', 'rgb(55, 231, 31)') + } +} + +function deleteProjectToDo(params, id) { + let swalWithBootstrapButtons = swal.mixin({ + customClass: { + confirmButton: 'btn btn-success', + cancelButton: 'btn btn-danger' + }, + buttonsStyling: false + }) + swalWithBootstrapButtons.fire({ + title: 'Are you sure?', + text: "You won't be able to revert this!", + type: 'warning', + showCancelButton: true, + confirmButtonText: 'Yes, delete it!', + cancelButtonText: 'No, cancel!', + reverseButtons: true + }) + .then((result) => { + if (result.value) { + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/${params}`, + method: 'DELETE', + headers: { + token: localStorage.getItem('token') + }, + data: { + id + } + }) + .then (result => { + generateProjectList() + swalWithBootstrapButtons.fire( + 'Deleted!', + 'Your ToDo has been deleted.', + 'success', + 2000 + ) + }) + .catch(() => { + swal.fire({ + type: 'error', + title: "You're unauthorize to delete this ToDo", + showConfirmButton: false, + timer: 2000 + }) + }) + } else if ( + result.dismiss === swal.DismissReason.cancel + ) { + swalWithBootstrapButtons.fire( + 'Cancelled', + 'Your ToDo is safe', + 'error' + ) + } + }) + .catch(() => { + swal.close() + swal.fire({ + type: 'error', + title: 'Failed deleting ToDO', + showConfirmButton: false, + timer: 2000 + }) + }) +} + +function showUpdateProjectToDo(params, id) { + $(`#UpdateToDoProject${params}`).empty() + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/get/${params}`, + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + $(`#UpdateToDoProject${params}`).append(` +
+
+

Update Your ToDo

+
+
+ + +
+
+ + +
+
+ + +
+ + +
+ `) + document.getElementById(`dueDateToDoProject${params}`).min = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().split("T")[0] + + $(`#updateToDoProjectForm${params}`).submit(e => { + e.preventDefault() + swal.fire({ + imageUrl:"https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-18.gif", + text:'Loading for Update...', + imageWidth: 200, + imageHeight: 200, + showConfirmButton: false + }) + + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/${params}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + title: $(`#titleToDoProject${params}`).val(), + description: $(`#descriptionToDoProject${params}`).val(), + dueDate: $(`#dueDateToDoProject${params}`).val() + } + }) + .done (result => { + $(`#updateToDoProject${params}`).empty() + showListToDoProject(id) + }) + .fail (err => { + }) + .always (() => { + swal.close() + }) + }) + }) + .fail (err => { + + }) + .always (() => { + swal.close() + }) +} + +function cancelUpdateToDo(params) { + $(`#UpdateToDoProject${params}`).empty() +} + +function doneProjectToDo (params, id) { + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/status/${params}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + status: true + } + }) + .done (result => { + $(`#${params}`).css('background-color', 'rgb(55, 231, 31)') + showListToDoProject(id) + }) + .fail (err => { + + }) +} + +function undoneProjectToDo (params, id) { + $.ajax({ + url: HOST_SERVER + `/Project/ToDo/status/${params}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + status: false + } + }) + .done (result => { + $(`#${params}`).css('background-color', '#ff0000') + showListToDoProject(id) + }) + .fail (err => { + }) +} + +function deleteProject(data) { + let swalWithBootstrapButtons = swal.mixin({ + customClass: { + confirmButton: 'btn btn-success', + cancelButton: 'btn btn-danger' + }, + buttonsStyling: false + }) + swalWithBootstrapButtons.fire({ + title: 'Are you sure?', + text: "You won't be able to revert this!", + type: 'warning', + showCancelButton: true, + confirmButtonText: 'Yes, delete it!', + cancelButtonText: 'No, cancel!', + reverseButtons: true + }) + .then((result) => { + if (result.value) { + $.ajax({ + url: HOST_SERVER + `/Project/${data}`, + method: 'DELETE', + headers: { + token: localStorage.getItem('token') + } + }) + .then(result => { + swalWithBootstrapButtons.fire( + 'Deleted!', + 'Your Project has been deleted.', + 'success', + 2000 + ) + generateProjectList() + }) + .catch(() => { + swal.fire({ + type: 'error', + title: "You're unauthorize to delete this Project", + showConfirmButton: false, + timer: 2000 + }) + }) + } else if ( + /* Read more about handling dismissals below */ + result.dismiss === swal.DismissReason.cancel + ) { + swalWithBootstrapButtons.fire( + 'Cancelled', + 'Your Project is safe', + 'error' + ) + } + }) + .catch(() => { + swal.close() + swal.fire({ + type: 'error', + title: 'Failed deleting Project', + showConfirmButton: false, + timer: 2000 + }) + }) +} + +function showCreateToDoProject(params) { + $(`#Project-content${params}`).empty() + $(`#Project-content${params}`).append(` +
+
+

Create Your ToDo Project

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ `) + document.getElementById(`dueDateToDo${params}`).min = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().split("T")[0] + $(`#FormToDo${params}`).submit(e => { + e.preventDefault() + let title = $(`#titleToDo${params}`).val() + let description = $(`#descriptionToDo${params}`).val() + let dueDate = $(`#dueDateToDo${params}`).val() + $.ajax({ + url: HOST_SERVER + '/Project/ToDo', + method: 'post', + headers: { + token: localStorage.getItem('token') + }, + data: { + title, + description, + dueDate, + _id: params + } + }) + .done (result => { + generateProjectList() + }) + .fail (err => { + }) + }) +} + +function showAddMemberForm(result) { + $(`#Project-content${result}`).empty() + $.ajax({ + url: HOST_SERVER, + method: 'get' + }) + .done (data => { + for (let i = 0; i < data.result.length; i++ ){ + $(`#Project-content${result}`).append(` +

${data.result[i].email}

+ + `) + } + }) + .fail (err => { + }) + .always (() => { + + }) +} + +function addMemberProject(member, _id) { + $.ajax({ + url: HOST_SERVER + `/Project/addMember/${_id}`, + method: 'post', + headers: { + token: localStorage.getItem('token') + }, + data: { + member + } + }) + .done (result => { + $(`#Project-content${_id}`).empty() + generateProjectList() + }) + .fail (err => { + swal.fire({ + type: 'error', + title: "You're unauthorize to addMember in this Project", + showConfirmButton: false, + timer: 2000 + }) + }) +} + +$('#FormProject').submit(e => { + e.preventDefault() + let name = $('#nameProject').val() + swal.fire({ + imageUrl:"https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-18.gif", + text:'Loading for Update...', + imageWidth: 200, + imageHeight: 200, + showConfirmButton: false + }) + $.ajax({ + url: HOST_SERVER + '/project', + method: 'post', + headers: { + token: localStorage.getItem('token') + }, + data: { + name + } + }) + .done (result => { + $('#FormProject').hide() + $('#nameProject').val('') + generateProjectList() + }) + .fail (err => { + }) + .always (() => { + swal.close() + }) +}) diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..d647e1a --- /dev/null +++ b/client/index.html @@ -0,0 +1,190 @@ + + + + + + + + + Fancy ToDo + + + + +
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+ + +
+
+ + +
+ +
+ Sign in with your accounts +
+ +
+
+
+
+
+
+
+
+
+
+
+ Weather Today +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+

Create Your ToDo

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+

Create Your Project

+
+
+ + +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/client/main.js b/client/main.js new file mode 100644 index 0000000..3435f44 --- /dev/null +++ b/client/main.js @@ -0,0 +1,532 @@ +var HOST_SERVER = 'http://localhost:3000' +$(document).ready(function() { + $('#NavBarStart').show() + $('#NavBarHome').hide() + $('#FormRegister').hide() + $('#FormLogin').show() + $('#content').hide() + $('#FormToDo').hide() + $('#FormProject').hide() + generateToDoList() + + document.getElementById('dueDateToDo').min = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().split("T")[0] + + if (localStorage.getItem('token')) { + $('#NavBarStart').hide() + $('#NavBarHome').show() + $('#FormRegister').hide() + $('#FormLogin').hide() + $('#content').show() + $('#FormToDo').hide() + $('#FormProject').hide() + } + + $('#FormRegister').submit(e => { + swal.fire({ + title: 'registering user', + allowOutsideClick: () => swal.isLoading(), + showConfirmButton: false + }) + let name = $('#nameRegister').val() + let email = $('#emailRegister').val() + let password = $('#passwordRegister').val() + e.preventDefault() + $.ajax({ + method: 'post', + url: HOST_SERVER + '/register', + data: { + name, + email, + password + } + }) + .done (result => { + swal.close() + swal.fire({ + type: 'success', + title: 'success to register', + showConfirmButton: false, + timer: 2000 + }) + $('#content').hide() + $('#FormRegister').hide() + $('#FormLogin').show() + }) + .fail (err => { + swal.close() + swal.fire({ + type: 'error', + title: 'REGISTER Failed ', + text: err.responseJSON, + showConfirmButton: false, + timer: 2000 + }) + }) + }) + + $('#FormLogin').submit(e => { + e.preventDefault() + swal.fire({ + title: 'logging user', + allowOutsideClick: () => swal.isLoading(), + showConfirmButton: false + }) + let email = $('#emailLogin').val() + let password = $('#passwordLogin').val() + $.ajax({ + url: HOST_SERVER + '/login', + method: 'post', + data: { + email, + password + } + }) + .done (result => { + swal.close() + swal.fire({ + type: 'success', + title: 'success to login', + showConfirmButton: false, + timer: 2000 + }) + $('#content').show() + $('#FormRegister').hide() + $('#FormLogin').hide() + $('#NavBarStart').hide() + $('#NavBarHome').show() + localStorage.setItem('token', result.token) + generateToDoList() + }) + .fail (err => { + swal.close() + swal.fire({ + type: 'error', + title: 'Login Failed ', + text: err.responseJSON, + showConfirmButton: false, + timer: 2000 + }) + }) + }) + + $('#FormToDo').submit(e => { + swal.fire({ + title: 'Creating ToDo', + allowOutsideClick: () => swal.isLoading(), + showConfirmButton: false + }) + let title = $('#titleToDo').val() + let description = $('#descriptionToDo').val() + let dueDate = $('#dueDateToDo').val() + e.preventDefault() + $.ajax({ + method: 'post', + url: HOST_SERVER + '/ToDo', + data: { + title, + description, + dueDate + }, + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + swal.close() + swal.fire({ + type: 'success', + title: 'success adding a ToDo', + showConfirmButton: false, + timer: 2000 + }) + $('#FormToDo').hide() + generateToDoList() + }) + .fail (err => { + swal.close() + swal.fire({ + type: 'error', + title: 'Failed adding a ToDo', + text: err.responseJSON, + showConfirmButton: false, + timer: 2000 + }) + }) + }) + + $('#showToDoForm').click(e => { + e.preventDefault() + $('#FormToDo').show() + $('#FormProject').hide() + $('#all-content').empty() + }) + + $('#showProjectForm').click(e => { + e.preventDefault() + $('#FormToDo').hide() + $('#FormProject').show() + $('#all-content').empty() + }) + + $('#searchToDo').submit(e => { + e.preventDefault() + let search = $('#searchTitle').val() + $.ajax({ + url: HOST_SERVER + `/ToDo/search/${search}`, + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + $('#all-content').empty() + cardToDoList(result) + $('#searchTitle').val('') + $('#FormProject').hide() + $('#FormToDo').hide() + }) + .fail (err => { + }) + }) +}) + +$('#FormWeather').submit(e => { + e.preventDefault() + let city = $('#location').val() + $.ajax({ + url: HOST_SERVER + `/weather/${city}`, + method: 'get' + }) + .done (result => { + $('#weatherCondition').empty() + $('#weatherCondition').append( ` +
+

Weather in: ${result.name}

+
+
Today Prediction :
+
+
${result.weather[0].main}
+
+

Detail :

+
+

${result.weather[0].description}

+
+

Temperature :

+
+

${result.main.temp}K

+
+

Pressure :

+
+

${result.main.pressure}mBar

+
+

Humidity :

+
+

${result.main.humidity}%

+
+

Wind Speed :

+
+

${result.wind.speed}m/s

+
+
+ + `) + }) + .fail (err => { + }) +}) + +function generateToDoList() { + $('#FormToDo').hide() + $('#FormProject').hide() + $('#all-content').empty() + swal.fire({ + imageUrl:"https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-18.gif", + text:'Loading Your ToDo List ...', + imageWidth: 200, + imageHeight: 200, + showConfirmButton: false + }) + $.ajax({ + url: HOST_SERVER + '/ToDo', + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + for (let i = 0; i < result.length; i++){ + cardToDoList(result[i]) + } + }) + .fail (err => { + }) + .always (() => { + swal.close() + }) +} + +function cardToDoList (result) { + $('#all-content').prepend(` +
+
+ ${result.title} +
+
+
${result.description}
+
+

Due Date : ${result.dueDate.slice(0, 10)}

+
+
+ +
+ `) + if (result.status == false) { + $(`#${result._id}`).css('background-color', '#ff0000') + } else { + $(`#${result._id}`).css('background-color', 'rgb(55, 231, 31)') + } +} + +function showUpdateToDo (id) { + $.ajax({ + url: HOST_SERVER + `/ToDo/${id}`, + method: 'get', + headers: { + token: localStorage.getItem('token') + } + }) + .done (result => { + $(`#${id}`).append(` +
+
+

Update Your ToDo

+
+
+ + +
+
+ + +
+
+ + +
+ + +
+ `) + document.getElementById(`dueDate${id}`).min = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().split("T")[0] + $(`#update${id}`).submit(e => { + e.preventDefault() + swal.fire({ + imageUrl:"https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-18.gif", + text:'Loading for Update...', + imageWidth: 200, + imageHeight: 200, + showConfirmButton: false + }) + + $.ajax({ + url: HOST_SERVER + `/ToDo/${id}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + title: $(`#title${id}`).val(), + description: $(`#description${id}`).val(), + dueDate: $(`#dueDate${id}`).val() + } + }) + .done (result => { + generateToDoList() + $(`#update${id}`).empty() + }) + .fail (err => { + }) + }) + }) +} + +function cancel(id) { + $(`#update${id}`).empty() +} + +function deleteToDo (_id) { + let swalWithBootstrapButtons = swal.mixin({ + customClass: { + confirmButton: 'btn btn-success', + cancelButton: 'btn btn-danger' + }, + buttonsStyling: false + }) + swalWithBootstrapButtons.fire({ + title: 'Are you sure?', + text: "You won't be able to revert this!", + type: 'warning', + showCancelButton: true, + confirmButtonText: 'Yes, delete it!', + cancelButtonText: 'No, cancel!', + reverseButtons: true + }) + .then((result) => { + if (result.value) { + $.ajax({ + url: HOST_SERVER + `/ToDo/${_id}`, + method: 'DELETE', + headers: { + token: localStorage.getItem('token') + } + }) + .then(result => { + swalWithBootstrapButtons.fire( + 'Deleted!', + 'Your ToDo has been deleted.', + 'success', + 2000 + ) + generateToDoList() + }) + .catch(() => { + swal.fire({ + type: 'error', + title: "You're unauthorize to delete this ToDo", + showConfirmButton: false, + timer: 2000 + }) + }) + } else if ( + /* Read more about handling dismissals below */ + result.dismiss === swal.DismissReason.cancel + ) { + swalWithBootstrapButtons.fire( + 'Cancelled', + 'Your ToDo is safe', + 'error' + ) + } + }) + .catch(() => { + swal.close() + swal.fire({ + type: 'error', + title: 'Failed deleting ToDO', + showConfirmButton: false, + timer: 2000 + }) + }) +} + +function doneToDo (params) { + $.ajax({ + url: HOST_SERVER + `/ToDo/status/${params}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + status: true + } + }) + .done (result => { + $(`#${params}`).css('background-color', 'rgb(55, 231, 31)') + }) + .fail (err => { + }) +} + +function undoneToDo (params) { + $.ajax({ + url: HOST_SERVER + `/ToDo/status/${params}`, + method: 'patch', + headers: { + token: localStorage.getItem('token') + }, + data: { + status: false + } + }) + .done (result => { + $(`#${params}`).css('background-color', '#ff0000') + }) + .fail (err => { + }) +} + +function showRegisterForm () { + $('#FormRegister').show() + $('#FormLogin').hide() +} + +function showLoginForm () { + $('#FormRegister').hide() + $('#FormLogin').show() +} + +function onSignIn(googleUser) { + swal.fire({ + title: 'Logging in user', + allowOutsideClick: () => swal.isLoading(), + showConfirmButton: false + }) + var id_token = googleUser.getAuthResponse().id_token + $.ajax({ + url: HOST_SERVER + '/loginGoogle', + method: 'post', + data: { + token: id_token + } + }) + .done (result => { + swal.close() + swal.fire({ + type: 'success', + title: 'User Logged In', + showConfirmButton: false, + timer: 2000 + }) + $('#NavBarStart').hide() + $('#NavBarHome').show() + $('#FormRegister').hide() + $('#FormLogin').hide() + $('#content').show() + localStorage.setItem('token', result.token) + generateToDoList() + }) + .fail (err => { + swal.close() + swal.fire({ + type: 'error', + title: 'REGISTER Failed ', + text: err.responseJSON, + showConfirmButton: false, + timer: 2000 + }) + }) + } + +function signOut() { + var auth2 = gapi.auth2.getAuthInstance() + localStorage.clear() + auth2.signOut().then(function () { + }) + $('#NavBarStart').show() + $('#NavBarHome').hide() + $('#FormRegister').hide() + $('#FormLogin').show() + $('#content').hide() + $('#all-content').empty() + swal.fire({ + type: 'success', + title: 'success to Logout', + showConfirmButton: false, + timer: 2000 + }) +} \ No newline at end of file diff --git a/server/apis/WeatherAPI.js b/server/apis/WeatherAPI.js new file mode 100644 index 0000000..fa8e4c7 --- /dev/null +++ b/server/apis/WeatherAPI.js @@ -0,0 +1,7 @@ +const axios = require('axios') + +const instance = axios.create({ + baseURL: 'https://api.openweathermap.org/data/2.5' +}) + +module.exports = instance \ No newline at end of file diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..72a8b7e --- /dev/null +++ b/server/app.js @@ -0,0 +1,33 @@ +require('dotenv').config() +const express = require('express') +const mongoose = require('mongoose') +const morgan = require('morgan') +const cors = require('cors') +const errorHandler = require('./middlewares/errorHandler') +const routers = require('./routers') +const PORT = process.env.PORT || 3000 +const app = express() + +app.use(express.json()) +app.use(express.urlencoded({ extended: true })) + +app.use(morgan('dev')) +app.use(cors()) + +const mongooseConfig = { + useNewUrlParser:true, + useFindAndModify: false, + useUnifiedTopology: true, + useCreateIndex: true +} + +mongoose.connect(process.env.MONGOOSE_URL, mongooseConfig, (err) => { + if(err) console.log(err) + console.log('database connected') +}) + +app.use('/', routers) +app.use(errorHandler) +app.listen(PORT, () => { + console.log(`you're listening to ${PORT}`) +}) \ No newline at end of file diff --git a/server/controllers/ProjectController.js b/server/controllers/ProjectController.js new file mode 100644 index 0000000..955f7de --- /dev/null +++ b/server/controllers/ProjectController.js @@ -0,0 +1,193 @@ +const Project = require('../models/Project') +const ToDo = require('../models/ToDo') + +class ProjectController { + static createProject (req, res, next) { + let { name } = req.body + let MemberId = req.loggedUser._id + Project.create({ + name, + MemberId, + OwnerId: MemberId + }) + .then (result => { + res.status(200).json(result) + }) + .catch (err => { + next(err) + }) + } + + static addMember (req, res, next) { + let addMember = req.body.member + let id = req.params.id + let OwnerId = req.loggedUser._id + let arrTemp = [] + let err + Project.findById(id) + .then (result => { + arrTemp = result.MemberId + if (result.OwnerId == OwnerId) { + return true + } else { + err = new Error (`You're unathorized to add member`) + err.name = 'Unauthorized' + next(err) + } + }) + .then (() => { + arrTemp.push(addMember) + return Project.findOneAndUpdate({ + _id: id + }, { + MemberId: arrTemp + }) + }) + .then (result => { + res.status(200).json(result) + }) + .catch (err => { + next(err) + }) + } + + static findProject (req, res, next) { + Project.find({ + MemberId: req.loggedUser._id + }) + .populate('OwnerId') + .then (result => { + res.status(200).json({ result }) + }) + .catch (err => { + next(err) + }) + } + + static deleteProject (req, res, next) { + let err + Project.findByIdAndDelete(req.params.id) + .then (result => { + res.status(200).json(result) + }) + .catch (() => { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } + + static createToDo (req, res, next) { + let { title, description, dueDate, _id } = req.body + ToDo.create({ + title, + description, + dueDate + }) + .then (result => { + return Project.findByIdAndUpdate(_id, {$push: { ToDoId: result._id }}) + }) + .then (result => { + res.status(201).json(result) + }) + .catch (err => { + next(err) + }) + } + + static deleteToDo (req, res, next) { + let { id } = req.body + let { ToDoId } = req.params + let arrTemp = [] + Project.findById(id) + .then (result => { + for (let i = 0; i < result.ToDoId.length; i++) { + if (ToDoId == result.ToDoId[i]) { + result.ToDoId.splice(i, 1) + } + } + arrTemp = result.ToDoId + return Project.findByIdAndUpdate(id, {$set: { + ToDoId: arrTemp + }}) + }) + .then (result => { + res.status(200).json(result) + }) + .catch (() => { + let err + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } + + static findAllToDo (req, res, next) { + let { id } = req.params + Project.findById(id) + .populate('ToDoId') + .then (result => { + if (result != null) res.status(200).json(result) + else { + let err + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + } + }) + .catch (err => { + next(err) + }) + } + + static findOneToDo (req, res, next) { + let { ToDoId } = req.params + let err + ToDo.findById(ToDoId) + .then (result => { + if (result) res.status(200).json(result) + else { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + } + }) + .catch (() => { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } + + static updateToDoStatus (req, res, next) { + let { status } = req.body + let { ToDoId } = req.params + ToDo.findByIdAndUpdate(ToDoId, {$set: { status }}) + .then (result => { + res.status(200).json(result) + }) + .catch (() => { + let err + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } + + static updateToDoAll (req, res, next) { + let { title, description, dueDate } = req.body + let { ToDoId } = req.params + ToDo.findByIdAndUpdate(ToDoId, {$set: { title, description, dueDate }}) + .then (result => { + res.status(200).json(result) + }) + .catch (() => { + let err + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } +} + +module.exports = ProjectController \ No newline at end of file diff --git a/server/controllers/ToDoController.js b/server/controllers/ToDoController.js new file mode 100644 index 0000000..fcca5ca --- /dev/null +++ b/server/controllers/ToDoController.js @@ -0,0 +1,124 @@ +const ToDo = require('../models/ToDo') + +class ToDoController { + static create (req, res, next) { + let { title, description, dueDate } = req.body + let UserId = req.loggedUser._id + ToDo.create({ + title, + description, + dueDate, + UserId + }) + .then (result => { + res.status(201).json(result) + }) + .catch (err => { + next(err) + }) + } + + static findAll (req, res, next) { + let UserId = req.loggedUser._id + ToDo.find({ + UserId + }) + .then (result => { + res.status(200).json(result) + }) + .catch (err => { + next(err) + }) + } + + static findOne (req, res, next) { + let { id } = req.params + let err + ToDo.findById(id) + .then (result => { + res.status(200).json(result) + }) + .catch (() => { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } + + static delete (req, res, next) { + let { id } = req.params + ToDo.findOneAndDelete({ + _id: id + }) + .then (result => { + if (result) res.status(200).json(result) + else { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + } + }) + .catch (err => { + next(err) + }) + } + + static updateStatus (req, res, next) { + let { id } = req.params + let { status } = req.body + ToDo.findOneAndUpdate({ + _id:id + }, { + status + }) + .then (result => { + res.status(200).json(result) + }) + .catch (err => { + next(err) + }) + } + + static updateToDo (req, res, next) { + let { id } = req.params + let { title, description, dueDate } = req.body + ToDo.findOneAndUpdate({ + _id:id + }, { + title, + description, + dueDate + }) + .then (result => { + res.status(200).json(result) + }) + .catch (err => { + next(err) + }) + } + + static searchToDo (req, res, next) { + let { title } = req.params + ToDo.find({ + UserId: req.loggedUser._id + }) + .then (data => { + if (data.length > 0) { + for (let el in data) { + if (data[el].title.includes(title)) { + res.status(200).json(data[el]) + } + } + } else { + let err = new Error ('ToDo User Masih Kosong') + err.name = 'DataError' + next(err) + } + }) + .catch (err => { + next(err) + }) + } +} + +module.exports = ToDoController \ No newline at end of file diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js new file mode 100644 index 0000000..6736020 --- /dev/null +++ b/server/controllers/UserController.js @@ -0,0 +1,116 @@ +const User = require('../models/User') +const { generateToken } = require('../helpers/jwt') +const { compare } = require('../helpers/bcrypt') +const { OAuth2Client } = require('google-auth-library') +const WeatherAPI = require('../apis/WeatherAPI') +const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID) + +class UserController { + static register (req, res, next) { + let { name, email, password } = req.body + User.create({ + name, + email, + password + }) + .then (result => { + res.status(201).json(result) + }) + .catch (err => { + next(err) + }) + } + + static login (req, res, next) { + let { email, password } = req.body + let err + let token + User.findOne({ + email + }) + .then (result => { + if (compare(password, result.password)) { + token = generateToken({ + _id: result._id + }) + res.status(200).json({token}) + } else { + console.log('masuk') + err = new Error('password salah') + err.name = 'PasswordError' + next(err) + } + }) + .catch (err => { + err = new Error('email tidak terdaftar') + err.name = 'EmailError' + next(err) + }) + } + + static googleLogin (req, res, next) { + let payload + let token + let err + client.verifyIdToken({ + idToken: req.body.token, + audience: process.env.GOOGLE_CLIENT_ID + }) + .then (ticket => { + payload = ticket.getPayload() + return User.findOne({ + email: payload.email + }) + }) + .then (result => { + if (result) { + token = generateToken({ + _id: result._id + }) + res.status(200).json({token}) + } else { + return User.create({ + email: payload.email, + name: payload.name, + password: payload.name + }) + } + }) + .then (result => { + token = generateToken({ + _id: result._id + }) + res.status(200).json({token}) + }) + .catch (() => { + err = new Error('Login Failure, Something Wrong With Connection') + err.name = 'GoogleError' + next(err) + }) + } + + static findAllUser (req, res, next) { + User.find() + .then (result => { + res.status(200).json({ result }) + }) + .catch (err => { + next(err) + }) + } + + static findWeather (req, res, next) { + WeatherAPI({ + url:`/weather?q=${req.params.city},id&appid=${process.env.WEATHER_API_KEY}`, + method: 'get' + }) + .then (({ data }) => { + res.status(200).json(data) + }) + .catch (err => { + next(err) + }) + } +} + +module.exports = UserController \ No newline at end of file diff --git a/server/helpers/bcrypt.js b/server/helpers/bcrypt.js new file mode 100644 index 0000000..a221fbd --- /dev/null +++ b/server/helpers/bcrypt.js @@ -0,0 +1,16 @@ +const bcrypt = require('bcryptjs') + +let salt = Number(process.env.SALT) + +function hashPassword (payload) { + return bcrypt.hashSync(payload, salt) +} + +function compare (payload, hashedPassword) { + return bcrypt.compareSync(payload, hashedPassword) +} + +module.exports = { + hashPassword, + compare +} \ No newline at end of file diff --git a/server/helpers/jwt.js b/server/helpers/jwt.js new file mode 100644 index 0000000..6a8d947 --- /dev/null +++ b/server/helpers/jwt.js @@ -0,0 +1,14 @@ +const jwt = require('jsonwebtoken') + +function generateToken (payload) { + return jwt.sign(payload, process.env.SECRET) +} + +function verify (payload) { + return jwt.verify(payload, process.env.SECRET) +} + +module.exports = { + generateToken, + verify +} diff --git a/server/middlewares/authentication.js b/server/middlewares/authentication.js new file mode 100644 index 0000000..8e1ebed --- /dev/null +++ b/server/middlewares/authentication.js @@ -0,0 +1,11 @@ +const { verify } = require('../helpers/jwt') + +module.exports = (req, res, next) => { + try { + let decoded = verify(req.headers.token) + req.loggedUser = decoded + next() + } catch (err) { + next(err) + } +} \ No newline at end of file diff --git a/server/middlewares/authorization.js b/server/middlewares/authorization.js new file mode 100644 index 0000000..fb56d1b --- /dev/null +++ b/server/middlewares/authorization.js @@ -0,0 +1,44 @@ +const Project = require('../models/Project') +const ToDo = require('../models/ToDo') + +module.exports = { + OwnerAuthorization (req, res, next) { + let { id } = req.params + let err + Project.findById(id) + .then (result => { + if (result.OwnerId == req.loggedUser._id) { + next() + } else { + err = new Error('Kamu Bukan Pemilik Project ini') + err.name = 'OwnerError' + next(err) + } + }) + .catch (() => { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + }, + ToDoAuthorization (req, res, next) { + let { id } = req.params + let err + ToDo.findById(id) + .populate('UserId') + .then (result => { + if (result.UserId._id == req.loggedUser._id) { + next() + } else { + err = new Error('Kamu tidak memiliki authorisasi') + err.name = 'Unauthorized' + next(err) + } + }) + .catch (err => { + err = new Error('Data Not Found') + err.name = 'DataError' + next(err) + }) + } +} \ No newline at end of file diff --git a/server/middlewares/errorHandler.js b/server/middlewares/errorHandler.js new file mode 100644 index 0000000..b666716 --- /dev/null +++ b/server/middlewares/errorHandler.js @@ -0,0 +1,59 @@ +module.exports = (err, req, res, next) => { + let status + let message + console.log(err) + console.log(err.name) + + switch (err.name) { + case 'ValidationError': + status = 400 + let arrMessage = [] + if (err.errors) { + for (let index in err.errors) { + arrMessage.push(err.errors[index].message) + } + } else { + arrMessage.push(err.message) + } + message = arrMessage + break + case 'MongoError': + status = 400 + message = "Email already registered" + break + case 'JsonWebTokenError': + status = 401 + message = "You need to Login First" + break + case 'PasswordError': + status = 400 + message = err.message + break + case 'EmailError': + status = 400 + message = err.message + break + case 'DataError': + status = 404 + message = err.message + break + case 'OwnerError': + status = 401 + message = err.message + break + case 'Unauthorized': + status = 401 + message = err.message + break + case 'GoogleError': + status = 400 + message = err.message + break + default: + status = 500 + message = 'Internal Server Error' + break + } + + res.status(status).json(message) +} \ No newline at end of file diff --git a/server/models/Project.js b/server/models/Project.js new file mode 100644 index 0000000..6041eda --- /dev/null +++ b/server/models/Project.js @@ -0,0 +1,37 @@ +const mongoose = require('mongoose') +const Schema = mongoose.Schema + +const ProjectSchema = new Schema({ + name: { + type: String, + required: [true, `Project Name can't be empty`] + }, + // description: { + // type: String, + // required: [true, `description can't be empty`] + // }, + // status: { + // type: Boolean + // }, + MemberId: [{ + type: Schema.Types.ObjectId, + ref: 'Users' + }], + OwnerId: { + type: Schema.Types.ObjectId, + ref: 'Users' + }, + ToDoId: [{ + type: Schema.Types.ObjectId, + ref: 'ToDos' + }] +}) + +ProjectSchema.pre('save', () => { + // this.status = false + this.MemberId.push(req.loggedUser._id) +}) + +const Project = mongoose.model('Projects', ProjectSchema) + +module.exports = Project \ No newline at end of file diff --git a/server/models/ToDo.js b/server/models/ToDo.js new file mode 100644 index 0000000..92cc122 --- /dev/null +++ b/server/models/ToDo.js @@ -0,0 +1,32 @@ +const mongoose = require('mongoose') +const Schema = mongoose.Schema + +const ToDoSchema = new Schema({ + title: { + type: String, + required: [true, `title can't be empty`] + }, + description: { + type: String, + required: [true, `description can't be empty`] + }, + status: { + type: Boolean + }, + UserId: { + type: Schema.Types.ObjectId, + ref: 'Users' + }, + dueDate: { + type: Date, + required: [true, `date can't be empty`] + } +}) + +ToDoSchema.pre('save', function() { + this.status = false +}) + +const ToDo = mongoose.model('ToDos', ToDoSchema) + +module.exports = ToDo \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js new file mode 100644 index 0000000..fb7e79c --- /dev/null +++ b/server/models/User.js @@ -0,0 +1,29 @@ +const mongoose = require('mongoose') +const { hashPassword } = require('../helpers/bcrypt') +const Schema = mongoose.Schema + +const UserSchema = new Schema ({ + email: { + required: [true, `email can't be empty`], + type: String, + match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please enter a valid email address. Must include "@" and "." '], + unique: [true, `email already registered`] + }, + name: { + type: String, + required: [true, `name can't be empty`], + minlength: [3, 'name must be longer or equal to 3 characters length'] + }, + password: { + type: String, + required: [true, `password can't be empty`], + } +}) + +UserSchema.pre('save', function () { + this.password = hashPassword(this.password) +}) + +const User = mongoose.model('Users', UserSchema) + +module.exports = User \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..c8cd43e --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,868 @@ +{ + "name": "server", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bson": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz", + "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "gaxios": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.1.0.tgz", + "integrity": "sha512-Gtpb5sdQmb82sgVkT2GnS2n+Kx4dlFwbeMYcDlD395aEvsLCSQXJJcHt7oJ2LrGxDEAeiOkK79Zv2A8Pzt6CFg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^3.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.2.0.tgz", + "integrity": "sha512-ympv+yQ6k5QuWCuwQqnGEvFGS7MBKdcQdj1i188v3bW9QLFIchTGaBCEZxSQapT0jffdn1vdt8oJhB5VBWQO1Q==", + "requires": { + "gaxios": "^2.0.1", + "json-bigint": "^0.3.0" + } + }, + "google-auth-library": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.5.1.tgz", + "integrity": "sha512-zCtjQccWS/EHYyFdXRbfeSGM/gW+d7uMAcVnvXRnjBXON5ijo6s0nsObP0ifqileIDSbZjTlLtgo+UoN8IFJcg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.2.0", + "gtoken": "^4.1.0", + "jws": "^3.1.5", + "lru-cache": "^5.0.0" + } + }, + "google-p12-pem": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.2.tgz", + "integrity": "sha512-UfnEARfJKI6pbmC1hfFFm+UAcZxeIwTiEcHfqKe/drMsXD/ilnVjF7zgOGpHXyhuvX6jNJK3S8A0hOQjwtFxEw==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", + "integrity": "sha512-wqyn2gf5buzEZN4QNmmiiW2i2JkEdZnL7Z/9p44RtZqgt4077m4khRgAYNuu8cBwHWCc6MsP6eDUn/KkF6jFIw==", + "requires": { + "gaxios": "^2.0.0", + "google-p12-pem": "^2.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + } + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "https-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", + "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kareem": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", + "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mongodb": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.3.tgz", + "integrity": "sha512-MdRnoOjstmnrKJsK8PY0PjP6fyF/SBS4R8coxmhsfEU7tQ46/J6j+aSHF2n4c2/H8B+Hc/Klbfp8vggZfI0mmA==", + "requires": { + "bson": "^1.1.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.7.tgz", + "integrity": "sha512-FU59waB4LKBa9KOnqBUcCcMIVRc09TFo1F8nMxrzSiIWATaJpjxxSSH5FBVUDxQfNdJLfg9uFHxaTxhhwjsZOQ==", + "requires": { + "bson": "~1.1.1", + "kareem": "2.3.1", + "mongodb": "3.3.3", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.6.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.1.2", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, + "mpath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz", + "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==" + }, + "mquery": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", + "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..74c55ff --- /dev/null +++ b/server/package.json @@ -0,0 +1,24 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "set NODE_ENV=development&& nodemon app.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.19.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "google-auth-library": "^5.5.1", + "jsonwebtoken": "^8.5.1", + "mongoose": "^5.7.7", + "morgan": "^1.9.1" + } +} diff --git a/server/routers/ProjectRouter.js b/server/routers/ProjectRouter.js new file mode 100644 index 0000000..0ef4f1e --- /dev/null +++ b/server/routers/ProjectRouter.js @@ -0,0 +1,18 @@ +const router = require('express').Router() +const ProjectController = require('../controllers/ProjectController') +const authentication = require('../middlewares/authentication') +const { OwnerAuthorization} = require('../middlewares/authorization') + +router.use(authentication) +router.post('/', ProjectController.createProject) +router.get('/', ProjectController.findProject) +router.delete('/:id', OwnerAuthorization, ProjectController.deleteProject) +router.post('/addMember/:id', OwnerAuthorization, ProjectController.addMember) +router.post('/ToDo', ProjectController.createToDo) +router.get('/ToDo/:id', ProjectController.findAllToDo) +router.patch('/ToDo/status/:ToDoId', ProjectController.updateToDoStatus) +router.patch('/ToDo/:ToDoId', ProjectController.updateToDoAll) +router.get('/ToDo/get/:ToDoId', ProjectController.findOneToDo) +router.delete('/ToDo/:ToDoId', ProjectController.deleteToDo) + +module.exports = router \ No newline at end of file diff --git a/server/routers/ToDoRouter.js b/server/routers/ToDoRouter.js new file mode 100644 index 0000000..32ff81d --- /dev/null +++ b/server/routers/ToDoRouter.js @@ -0,0 +1,15 @@ +const router = require('express').Router() +const ToDoController = require('../controllers/ToDoController') +const authentication = require('../middlewares/authentication') +const { ToDoAuthorization } = require('../middlewares/authorization') + +router.use(authentication) +router.post('/', ToDoController.create) +router.get('/', ToDoController.findAll) +router.patch('/status/:id', ToDoAuthorization, ToDoController.updateStatus) +router.delete('/:id', ToDoAuthorization, ToDoController.delete) +router.patch('/:id', ToDoAuthorization, ToDoController.updateToDo) +router.get('/:id', ToDoAuthorization, ToDoController.findOne) +router.get('/search/:title', ToDoController.searchToDo) + +module.exports = router \ No newline at end of file diff --git a/server/routers/UserRouter.js b/server/routers/UserRouter.js new file mode 100644 index 0000000..6be7155 --- /dev/null +++ b/server/routers/UserRouter.js @@ -0,0 +1,10 @@ +const router = require('express').Router() +const UserController = require('../controllers/UserController') + +router.post('/loginGoogle', UserController.googleLogin) +router.post('/register', UserController.register) +router.post('/login', UserController.login) +router.get('/weather/:city', UserController.findWeather) +router.get('/', UserController.findAllUser) + +module.exports = router \ No newline at end of file diff --git a/server/routers/index.js b/server/routers/index.js new file mode 100644 index 0000000..431111d --- /dev/null +++ b/server/routers/index.js @@ -0,0 +1,10 @@ +const router = require('express').Router() +const UserRouter = require('./UserRouter') +const ProjectRouter = require('./ProjectRouter') +const ToDoRouter = require('./ToDoRouter') + +router.use('/', UserRouter) +router.use('/ToDo', ToDoRouter) +router.use('/Project', ProjectRouter) + +module.exports = router \ No newline at end of file