diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 7e08b13..23f0d7b --- a/README.md +++ b/README.md @@ -1 +1,322 @@ -# fancy-todo \ No newline at end of file +# Fancy To Do +# Author : Luky Winata + +**How To** + +``` +Client Site: http://localhost:8080/ +Server Site: http://35.197.153.90 / +npm init -y (inside root server folder) +npm run dev (on terminal inside root server folder) +live-server --host=localhost (on terminal inside root client folder) +.env-template: PORT, DB_URI, JWT_SECRET, CLIENT_ID (Google), API_KEY (Air Visual) +``` + +**List of Routes User:** + +| **Route** | **HTTP** | **Description** | +| ----------------- | -------- | ------------------------------------------- | +| /user/register | POST | Sign up with new user info | +| /user/login | POST | Sign in and get an access token | +| /user/googleLogin | POST | Sign in with google and get an access token | + +**List of Routes Task:** + +| **Route** | **HTTP** | **Description** | **Notes** | +| --------- | -------- | ------------------------------- | ----------------------- | +| /task | POST | Create a task | Authenticated user only | +| /task | GET | Get all tasks' info | Authenticated user only | +| /task/:id | GET | Get a single task's detail | Authorized user only | +| /task/:id | PUT | Update a task with new details | Authorized user only | +| /task/:id | PATCH | Undo a task from Done to Active | Authorized user only | +| /task/:id | DELETE | Delete a task | Authorized user only | + +**Errors:** + +| Code | Name | Description | +| ---- | --------------------- | --------------------- | +| 400 | Bad Request | Client's Mistake | +| 401 | Authentication Failed | Unauthorized Access | +| 403 | Unauthorized Access | Invalid Access Token | +| 500 | Internal Server Error | Internal Error | + +**400:** +``` +{ + [ + "Name is required", + "E-mail is required", + "Password is required" + ] +} +``` + +**401:** +``` +{ + "message": "User is unauthorized for this access" +} +``` + +**403:** +``` +{ + "message": "User's session has been expired" +} +``` + +**500:** + +``` +{ + "message": "Internal Server Error" +} +``` + +**Register User** +---- +* **URL:** `/user/register` + +* **Method:** `POST` + +* **Body Data:** + + ``` + name: Luky Winata + email: lukywinata@outlook.com + password: 12345678 + ``` + +* **Success Response:** + + * **Status:** 201 + **Content:** + + ``` + { + "_id": "5dbd4a607ad5b61201781b95", + "name": "Luky Winata", + "email": "lukywinata@outlook.com", + "password": "$2a$10$dSgyVFM47duFThRKlEBJG.paG2GMt.pMbcsBKgWcjlfDVSZQmLdO6", + "__v": 0 + } + ``` + + +**Login User** +---- + +* **URL:** `/user/login` + +* **Method:** `POST` + +* **Body Data:** + + ``` + email: lukywinata@outlook.com + password: 12345678 + ``` + +* **Success Response:** + + * **Status:** 200 + **Content:** + + ``` + { + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } + ``` + + +**Add New Task** +---- + +* **URL:** `/task` + +* **Method:** `POST` + +* **Headers:** `access_token = [string]` + +* **Body Data:** + + ``` + title: Hacktiv8 Phase 2 + description: (allow empty) + dueDate: 30 November 2019 + ``` + +* **Success Response:** + + * **Status:** 201 + **Content:** + + ``` + { + "_id": "5dbd56c73b90dd0eaaf71f10", + "title": "Hacktiv8 Phase 2", + "description": "", + "status": "Active", + "dueDate": "2019-11-29T17:00:00.000Z", + "UserId": "5dbd3fb1dd9f80375017fff9", + "createdAt": "2019-11-02T10:13:27.019Z", + "updatedAt": "2019-11-02T10:13:27.019Z", + "__v": 0 + } + ``` + + +**List Tasks** +---- + +* **URL:** `/task/` + +* **Method:** `GET` + +* **Headers:** `access_token = [string]` + +* **Success Response:** + + * **Status:** 201 + **Content:** + + ``` + [ + { + "_id": "5dbd504808bd83165a1263b0", + "title": "Angular", + "description": "", + "status": "Done", + "dueDate": null, + "UserId": "5dbd3fb1dd9f80375017fff9", + "createdAt": "2019-11-02T09:45:44.885Z", + "updatedAt": "2019-11-02T10:09:46.633Z", + "__v": 0 + }, + ...... + ] + ``` + + +**Get Task's Detail** +---- + +* **URL:** `/task/:id` + +* **Method:** `GET` + +* **Headers:** `access_token = [string]` + +* **Params:** `id = [string]` + +* **Success Response:** + + * **Status:** 200 + **Content:** + + ``` + [ + { + "_id": "5dbd504808bd83165a1263b0", + "title": "Angular", + "description": "", + "status": "Done", + "dueDate": null, + "UserId": "5dbd3fb1dd9f80375017fff9", + "createdAt": "2019-11-02T09:45:44.885Z", + "updatedAt": "2019-11-02T10:09:46.633Z", + "__v": 0 + }, + ...... + ] + ``` + + +**Update Task** +---- + +* **URL:** `/task/:id` + +* **Method:** `PUT` + +* **Headers:** `access_token = [string]` + +* **Params:** `id = [string]` + +* **Success Response:** + + * **Status:** 200 + **Content:** + + ``` + [ + { + "_id": "5dbd504808bd83165a1263b0", + "title": "Angular", + "description": "", + "status": "Done", + "dueDate": null, + "UserId": "5dbd3fb1dd9f80375017fff9", + "createdAt": "2019-11-02T09:45:44.885Z", + "updatedAt": "2019-11-02T10:09:46.633Z", + "__v": 0 + }, + ...... + ] + ``` + + +**Undo Task** +---- + +* **URL:** `/task/:id` + +* **Method:** `PATCH` + +* **Headers:** `access_token = [string]` + +* **Params:** `id = [string]` + +* **Success Response:** + + * **Status:** 200 + **Content:** + + ``` + { + "_id": "5dbeb98459a4341f896bb00f", + "title": "Belajar Javascript", + "description": "Hacktiv8", + "status": "Active", + "dueDate": "2019-11-30T00:00:00.000Z", + "UserId": "5dbeb5ed59a4341f896bb00d", + "createdAt": "2019-11-03T11:27:00.790Z", + "updatedAt": "2019-11-03T11:27:00.790Z", + "__v": 0 + } + ``` + + +**Delete Task** +---- + +* **URL:** `/task/:id` + +* **Method:** `DELETE` + +* **Headers:** `access_token = [string]` + +* **Params:** `id = [string]` + +* **Success Response:** + + * **Status:** 200 + **Content:** + + ``` + { + "n": 1, + "ok": 1, + "deletedCount": 1 + } + ``` \ No newline at end of file diff --git a/client/img/picture.png b/client/img/picture.png new file mode 100755 index 0000000..bdff66d Binary files /dev/null and b/client/img/picture.png differ diff --git a/client/index.html b/client/index.html new file mode 100755 index 0000000..580d07c --- /dev/null +++ b/client/index.html @@ -0,0 +1,188 @@ + + + + + + + + + To-Do-Fancy-Luky + + + + + + +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/client/js/date.js b/client/js/date.js new file mode 100755 index 0000000..d2d2bff --- /dev/null +++ b/client/js/date.js @@ -0,0 +1,71 @@ +// Date +function formatDate(date) { + let dates = date.split(' '); + let dd = Number(dates[2]); + let yy = Number(dates[3]); + let day = null; + switch (dates[0]) { + case 'Mon': + day = 'Monday'; + break; + case 'Tue': + day = 'Tuesday' + break; + case 'Wed': + day = 'Wednesday' + break; + case 'Thu': + day = 'Thursday' + break; + case 'Fri': + day = 'Friday' + break; + case 'Sat': + day = 'Saturday' + break; + case 'Sun': + day = 'Sunday' + break; + } + let mm = null; + switch (dates[1]) { + case 'Jan': + mm = 'Jan'; + break; + case 'Feb': + mm = 'Feb' + break; + case 'Mar': + mm = 'Mar' + break; + case 'Apr': + mm = 'Apr'; + break; + case 'May': + mm = 'May' + break; + case 'Jun': + mm = 'Jun' + break; + case 'Jul': + mm = 'Jul'; + break; + case 'Aug': + mm = 'Aug' + break; + case 'Sep': + mm = 'Sep' + break; + case 'Oct': + mm = 'Oct' + break; + case 'Nov': + mm = 'Nov' + break; + case 'Dec': + mm = 'Dec' + break; + } + return `${day}, ${dd} ${mm} ${yy}` +} + diff --git a/client/js/main.js b/client/js/main.js new file mode 100755 index 0000000..b7471ce --- /dev/null +++ b/client/js/main.js @@ -0,0 +1,154 @@ +$(document).ready(function () { + + $("#user-name").text(`Hello, ${localStorage.getItem('name')}`) + let session = localStorage.getItem('isLogin') + let token = localStorage.getItem('access_token') + let user = localStorage.getItem('name') + // First Page + if (!(session && token && user)) { + $("#login-content").show() + } + + // Register Form + $("#trigger-register").click(event => { + event.preventDefault() + $("#login-content").hide() + $("#register-content").fadeIn(200) + }) + $("#register-form").submit(() => { + event.preventDefault() + let name = $("#register-name").val() + let email = $("#register-email").val() + let password = $("#register-password").val() + webRegister(name, email, password) + $("#register-form")[0].reset() + }) + + // Login Form + $("#trigger-login").click(event => { + event.preventDefault() + $("#register-content").hide() + $("#login-content").fadeIn(200) + }) + $("#login-form").submit(() => { + event.preventDefault() + let email = $("#login-email").val() + let password = $("#login-password").val() + webLogin(email, password) + $("#login-form")[0].reset() + }) + + // After Register and Login Page + if (session && token && user) { + $("#todo-menu").show() + $("#signout").show() + } + + // My Day + $("#my-day").click(event => { + event.preventDefault() + $("#todo-menu").hide() + showListedTask() + }) + $("#back-my-day").click(event => { + event.preventDefault() + $("#my-day-menu").hide() + $("#todo-menu").fadeIn(200) + }) + + // Air Visual API + $.ajax({ + url: 'http://35.197.153.90/api', + method: 'GET' + }) + .done(result => { + $("#air-visual").append(` +   ${result.data.city}, ${result.data.country}
+   ${Number(result.data.current.weather.tp)} oC
+   ${result.data.current.pollution.aqius} (Pollution - US AQI) + `) + }) + .fail(err => { + console.log(err) + }) + + // Add Task + $("#add-task").click(event => { + event.preventDefault() + $("#my-day-menu").hide() + $("#add-task-menu").fadeIn(200) + }) + $("#back-add-task").click(event => { + event.preventDefault() + $("#add-task-menu").hide() + showListedTask() + }) + + // Search Bar + $('#search-bar').keyup(function () { + let filter = $('#search-bar').val().toUpperCase(); + let tasks = $('#my-day-content li'); + let data = $('#my-day-content li h5'); + for (let i = 0; i < tasks.length; i++) { + if (!data[i].innerText.toUpperCase().includes(filter)) { + tasks[i].style.display = "none"; + } + else { + tasks[i].style.display = "" + } + } + }); + + // Add Task Form + $("#add-task-form").submit(() => { + event.preventDefault() + let title = $("#task-title").val() + let description = $("#task-description").val() + let dueDate = $("#task-date").val() + $("#add-task-form")[0].reset() + $.ajax({ + url: 'http://35.197.153.90/task', + method: 'POST', + data: { + title, + description, + dueDate + }, + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(response => { + $("#add-task-menu").hide() + showListedTask() + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) + }) + + //Edit Task + $("#back-edit-task").click(event => { + event.preventDefault() + $("#edit-task-content").empty() + $("#edit-task-menu").hide() + showListedTask() + }) + + // Archived + $("#archived").click(event => { + event.preventDefault() + $("#todo-menu").hide() + showArchivedTask() + }) + $("#back-archived").click(event => { + event.preventDefault() + $("#archived-menu").hide() + $("#todo-menu").fadeIn(200) + }) + // End Document Ready +}) diff --git a/client/js/task.js b/client/js/task.js new file mode 100755 index 0000000..738164c --- /dev/null +++ b/client/js/task.js @@ -0,0 +1,251 @@ +// Patch Task from Appended AJAX Data +$('body').on('click', '.undotask', (event) => { + Swal.fire({ + title: 'Undo', + text: "Do you want to undone this task?", + type: 'question', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: 'Yes' + }) + .then((result) => { + if (result.value) { + $.ajax({ + url: `http://35.197.153.90/task/${event.currentTarget.id}`, + method: 'PATCH', + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(response => { + Swal.fire( + 'Success!', + 'Your file has been moved to My Day', + 'success' + ) + showArchivedTask() + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) + } + }) +}) + +// Delete Task from Appended AJAX Data +$('body').on('click', '.deletetask', (event) => { + Swal.fire({ + title: 'Delete', + text: "Do you want to delete this task?", + type: 'question', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: 'Yes, delete it!' + }) + .then((result) => { + if (result.value) { + $.ajax({ + url: `http://35.197.153.90/task/${event.currentTarget.id}`, + method: 'DELETE', + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(response => { + Swal.fire( + 'Deleted!', + 'Your file has been deleted permanently', + 'success' + ) + showArchivedTask() + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) + } + }) +}) + +// Submit Edited Task from Appended AJAX Data +$('body').on('submit', "#edit-task-form", event => { + // Swal.showLoading() + let id = event.currentTarget[0].value + let title = event.currentTarget[1].value + let description = event.currentTarget[2].value + let status = event.currentTarget[3].value + let dueDate = new Date(event.currentTarget[4].value) + $.ajax({ + url: `http://35.197.153.90/task/${id}`, + method: 'PUT', + data: { + title, + description, + status, + dueDate + }, + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(response => { + $("#edit-task-content").empty() + $("#edit-task-menu").hide() + showListedTask() + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +}) + +// Detailed Task from Appended AJAX Data +$('body').on('click', '.taskdetail', (event) => { + let taskId = event.currentTarget.id + $.ajax({ + url: `http://35.197.153.90/task/${taskId}`, + method: 'GET', + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(response => { + $("#my-day-menu").hide() + let date = new Date(response.dueDate).toISOString().substr(0, 10) + $("#edit-task-content").append(` +
+
+ +
+
+ Title             
+
+ +
+
+
+
Description
+
+ +
+
+
+
Status         
+
+ +
+
+
+
Due Date   
+
+ +
+
+ +
+
+ `) + $("#edit-task-menu").fadeIn(500) + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +}) + +// Archived Listed Tasks +function showArchivedTask() { + $("#archived-content").empty() + $.ajax({ + url: 'http://35.197.153.90/task', + method: 'GET', + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(tasks => { + for (let i = 0; i < tasks.length; i++) { + if (tasks[i].status === 'Done') { + $("#archived-content").append(` +
  • +
    + +
    + + +
    +
    +
  • + `) + } + } + $("#archived-menu").fadeIn(500) + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +} + +// My Day Listed Tasks +function showListedTask() { + let todayDate = new Date().toDateString(); + $("#my-day-nav").text(`${formatDate(todayDate)}`) + $("#my-day-content").empty() + $.ajax({ + url: 'http://35.197.153.90/task', + method: 'GET', + headers: { + access_token: localStorage.getItem('access_token') + } + }) + .done(tasks => { + let colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info'] + let colorIndex = 0 + for (let i = 0; i < tasks.length; i++) { + let dueDate = new Date(tasks[i].dueDate).toDateString() + if (colorIndex === 5) { + colorIndex = 0 + } + if (tasks[i].status === 'Active') { + $("#my-day-content").append(` +
  • +
    ${tasks[i].title}
    +
    Deadline: ${formatDate(dueDate)}
    +
  • + `) + colorIndex++ + } + } + $("#my-day-menu").fadeIn(500) + }) + .fail(err => { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +} diff --git a/client/js/user.js b/client/js/user.js new file mode 100755 index 0000000..2311546 --- /dev/null +++ b/client/js/user.js @@ -0,0 +1,84 @@ +function onSignIn(googleUser) { + const idToken = googleUser.getAuthResponse().id_token; + $.ajax({ + url: `http://35.197.153.90/user/googleLogin`, + method: 'POST', + data: { + idToken: idToken + } + }) + .done(function (response) { + loginSuccess(response) + }) + .fail(function (err) { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +} + +function signOut() { + const auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(()=>{}) + localStorage.clear() + $("#todo-menu").hide() + $("#signout").hide() + $("#login-content").fadeIn() +} + +function webRegister(name, email, password) { + $.ajax({ + url: `http://35.197.153.90/user/register`, + method: 'POST', + data: { + name, + email, + password + } + }) + .done(function (response) { + $("#register-content").hide() + $("#login-content").fadeIn() + }) + .fail(function (err) { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +} + +function webLogin(email, password) { + $.ajax({ + url: `http://35.197.153.90/user/login`, + method: 'POST', + data: { + email, + password + } + }) + .done(function (response) { + loginSuccess(response) + }) + .fail(function (err) { + Swal.fire({ + type: 'error', + title: 'Error', + text: `${err.responseJSON.message}`, + }) + }) +} + +function loginSuccess(response) { + localStorage.setItem('access_token', response.access_token) + localStorage.setItem('isLogin', response.isLogin) + localStorage.setItem('name', response.name) + $("#register-content").hide() + $("#login-content").hide() + $("#signout").show() + $("#todo-menu").fadeIn() + $("#user-name").text(`Hello, ${localStorage.getItem('name')}`) +} \ No newline at end of file diff --git a/client/style.css b/client/style.css new file mode 100755 index 0000000..1b499d3 --- /dev/null +++ b/client/style.css @@ -0,0 +1,49 @@ +.nav-text{ + font-size: 1.2rem; + width: 50%; + padding: 5% 0; +} + +.g-signin2{ + width: 100%; + margin-top: 5%; +} + +.g-signin2 > div{ + margin: 0 auto; +} + +.alert { + color: white; + font-size: 1.2rem; + border-radius: 10px +} + +.alert:hover { + box-shadow: + 1px 1px #fff, + 2px 2px #fff, + 3px 3px #fff; + -webkit-transform: translateX(-3px); + transform: translateX(-3px); +} + +.user-name { + font-size: 1.2rem; + font-weight: bold; + margin-bottom: 1.5rem; +} + +.my-day-nav { + font-size: 1.2rem; + font-weight: bold; +} + +.my-day-content { + height: 40vh; + overflow-y: auto; +} + +.task-detail { + color: black +} \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100755 index 0000000..dbb896a --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +credentials.json \ No newline at end of file diff --git a/server/app.js b/server/app.js new file mode 100755 index 0000000..d5236d7 --- /dev/null +++ b/server/app.js @@ -0,0 +1,30 @@ +if (process.env.NODE_ENV === 'development') { + require('dotenv').config() +} + +const cors = require('cors') +const mongoose = require('mongoose') +const express = require('express') +const app = express() + +mongoose.connect(process.env.MONGODB_URL, { + useNewUrlParser: true, useUnifiedTopology: true, + useCreateIndex: true, useFindAndModify: false +}) + .then(success => { + console.log(`connected to mongodb`) + }) + .catch(err => { + console.log(`cannot connect to mongodb`) + }) + +app.use(cors()) +app.use(express.json()) +app.use(express.urlencoded({ extended: false })) + +app.use('/', require('./routes')) +app.use(require('./middlewares/errorHandler')) + +app.listen(process.env.PORT, () => { + console.log(`listening on port ${process.env.PORT}`) +}) diff --git a/server/controllers/apiController.js b/server/controllers/apiController.js new file mode 100755 index 0000000..47d28bc --- /dev/null +++ b/server/controllers/apiController.js @@ -0,0 +1,16 @@ +const axios = require('axios') + +class ApiController { + static get(req, res, next) { + axios({ + method: 'get', + url: `http://api.airvisual.com/v2/nearest_city?key=${process.env.API_KEY}` + }) + .then(({ data }) => { + res.status(200).json(data) + }) + .catch(next) + } +} + +module.exports = ApiController \ No newline at end of file diff --git a/server/controllers/taskController.js b/server/controllers/taskController.js new file mode 100755 index 0000000..01b9e13 --- /dev/null +++ b/server/controllers/taskController.js @@ -0,0 +1,84 @@ +const Task = require('../models/task') + +class TaskController { + static list(req, res, next) { + Task.find({ UserId: req.user.id }) + .then(results => { + results.sort((a, b) => a.dueDate - b.dueDate) + res.status(200).json(results) + }) + .catch(next) + } + static add(req, res, next) { + let { title, description, dueDate } = req.body + let today = new Date() + let reg = null + if (!dueDate) { + let err = new Error('Date must be inputted') + next(err) + } else { + reg = new Date(dueDate) + } + if (reg.getDate() < today.getDate()) { + let err = new Error('Date must be same or greater than today') + next(err) + } + let taskData = { + title, + description, + status: 'Active', + dueDate, + UserId: req.user.id + } + Task.create(taskData) + .then(task => { + res.status(201).json(task) + }) + .catch(next) + } + static detail(req, res, next) { + Task.findOne({ _id: req.params.id }) + .then(result => { + res.status(200).json(result) + }) + .catch(next) + } + static update(req, res, next) { + let { title, description, status, dueDate } = req.body + Task.findOneAndUpdate( + { _id: req.params.id }, + { title, description, status, dueDate } + ) + .then(task => { + res.status(200).json(task) + }) + .catch(next) + } + static undo(req, res, next) { + Task.findOneAndUpdate( + { _id: req.params.id }, + { status: 'Active' } + ) + .then(task => { + res.status(200).json(task) + }) + .catch(next) + } + static delete(req, res, next) { + Task.findByIdAndDelete({ _id: req.params.id }) + .then(task => { + res.status(200).json(task) + }) + .catch(next) + } + static search(req, res, next) { + Task.find({'title': {'$regex': req.query.title, '$options': 'i'}}) + .then(books => { + res.status(200).json(books) + }) + .catch(next) + } + +} + +module.exports = TaskController \ No newline at end of file diff --git a/server/controllers/userController.js b/server/controllers/userController.js new file mode 100755 index 0000000..c696546 --- /dev/null +++ b/server/controllers/userController.js @@ -0,0 +1,74 @@ +const User = require('../models/user') +const bcrypt = require('../helpers/bcrypt') +const jwt = require('../helpers/jwt') + +class UserController { + static register(req, res, next) { + let { name, email, password } = req.body + let userData = { name, email, password } + User.findOne({ email }) + .then(user => { + if (user) { + let err = new Error('Email is already in use') + err.code = 400 + next(err) + } else { + return User.create(userData) + } + }) + .then(user => { + res.status(201).json(user) + }) + .catch(next) + } + static login(req, res, next) { + User.findOne({ email: req.body.email }) + .then(user => { + if(!user) { + let err = new Error('User is not found') + err.code = 401 + next(err) + } + let isCorrect = bcrypt.compare(req.body.password, user.password) + if (user && isCorrect) { + let access_token = jwt.generate({ id: user._id, email: user.email }) + res.status(200).json({ + access_token, + name: user.name, + isLogin: true + }) + } else { + let err = new Error('Email or Password is incorrect') + err.code = 401 + next(err) + } + }) + .catch(next) + } + static googleLogin(req, res, next) { + let { name, email } = req.decoded + User.findOne({ email }) + .then(user => { + if (user) { + return user + } else { + return User.create({ + name, + email, + password: 'default' + }) + } + }) + .then(user => { + let access_token = jwt.generate({ id: user._id, email: user.email }) + res.status(200).json({ + access_token, + name: user.name, + isLogin: true + }) + }) + .catch(next) + } +} + +module.exports = UserController \ No newline at end of file diff --git a/server/helpers/bcrypt.js b/server/helpers/bcrypt.js new file mode 100755 index 0000000..3cd071c --- /dev/null +++ b/server/helpers/bcrypt.js @@ -0,0 +1,11 @@ +const bcrypt = require('bcryptjs') + +module.exports = { + hash: (password) => { + let salt = bcrypt.genSaltSync(10) + return bcrypt.hashSync(password, salt) + }, + compare: (input, database) => { + return bcrypt.compareSync(input, database) + } +} \ No newline at end of file diff --git a/server/helpers/jwt.js b/server/helpers/jwt.js new file mode 100755 index 0000000..8562c21 --- /dev/null +++ b/server/helpers/jwt.js @@ -0,0 +1,10 @@ +const jwt = require('jsonwebtoken') + +module.exports = { + generate: (payload) => { + return jwt.sign(payload, process.env.JWT_SECRET) + }, + verify: (token) => { + return jwt.verify(token, process.env.JWT_SECRET) + } +} \ No newline at end of file diff --git a/server/middlewares/authenticate.js b/server/middlewares/authenticate.js new file mode 100755 index 0000000..acb3bfe --- /dev/null +++ b/server/middlewares/authenticate.js @@ -0,0 +1,24 @@ +const User = require('../models/user') +const jwt = require('../helpers/jwt') + +module.exports = (req, res, next) => { + try { + let verified = jwt.verify(req.headers.access_token) + req.user = verified + User.findOne({ _id: req.user.id }) + .then(result => { + if (result) { + next() + } else { + let err = new Error('User is not found') + next(err) + } + }) + .catch(err => { + next(err) + }) + } catch (err) { + err.name = 'jsonwebtoken' + next(err) + } +} \ No newline at end of file diff --git a/server/middlewares/authorize.js b/server/middlewares/authorize.js new file mode 100755 index 0000000..b4d73a2 --- /dev/null +++ b/server/middlewares/authorize.js @@ -0,0 +1,23 @@ +const Task = require('../models/task') + +module.exports = (req, res, next) => { + Task.findOne({ _id: req.params.id}) + .then(result => { + if(!result) { + let err = new Error('Task is not found') + err.code(400) + next(err) + } else { + if(String(result.UserId) === String(req.user.id)) { + next() + } else { + let err = new Error ('You are not authorized') + err.code = 403 + next(err) + } + } + }) + .catch(err => { + next (err) + }) +} \ No newline at end of file diff --git a/server/middlewares/errorHandler.js b/server/middlewares/errorHandler.js new file mode 100755 index 0000000..0efdb3e --- /dev/null +++ b/server/middlewares/errorHandler.js @@ -0,0 +1,26 @@ +module.exports = (err, req, res, next) => { + let code; + let message; + switch (err.name) { + case 'ValidationError': + code = 400; + let arr = []; + for (let i in err.errors) { + arr.push(err.errors[i].message); + } + message = arr; + break; + case 'JsonWebTokenError': + code = 401; + message = err.message; + break; + default: + code = err.code || 500; + message = err.message || 'Internal Server Error'; + break; + } + res.status(code).json({ + code, + message + }); +} \ No newline at end of file diff --git a/server/middlewares/googleAuth.js b/server/middlewares/googleAuth.js new file mode 100755 index 0000000..7a6f62e --- /dev/null +++ b/server/middlewares/googleAuth.js @@ -0,0 +1,17 @@ +const { OAuth2Client } = require('google-auth-library'); +const client = new OAuth2Client(process.env.CLIENT_ID); + +module.exports = (req, res, next) => { + client.verifyIdToken({ + idToken: req.body.idToken, + audience: process.env.CLIENT_ID, + }) + .then(ticket => { + let payload = ticket.getPayload() + req.decoded = payload + next() + }) + .catch(err => { + next(err) + }) +} \ No newline at end of file diff --git a/server/models/task.js b/server/models/task.js new file mode 100755 index 0000000..7c5ca4a --- /dev/null +++ b/server/models/task.js @@ -0,0 +1,27 @@ +'use strict' + +const { Schema, model } = require('mongoose'); + +const taskSchema = new Schema({ + title: { + type: String, + required: [true, `Please input the task's title`] + }, + description: { + type: String + }, + status: { + type: String + }, + dueDate: { + type: Date + }, + UserId: { + type: Schema.Types.ObjectId, + ref: 'User' + } +}, { timestamps: true }); + +const Task = model('Task', taskSchema); + +module.exports = Task; \ No newline at end of file diff --git a/server/models/user.js b/server/models/user.js new file mode 100755 index 0000000..d8cbfcf --- /dev/null +++ b/server/models/user.js @@ -0,0 +1,51 @@ +const { Schema, model } = require('mongoose'); +const bcrypt = require('../helpers/bcrypt') + +const userSchema = new Schema({ + name: { + type: String, + required: [true, 'Please input your name'] + }, + email: { + type: String, + required: [true, 'Please input your email address'], + validate: [{ + validator: function (value) { + let emailRegex = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/ + return emailRegex.test(value) + }, + message: props => `Email ${props.value} is not a valid email address` + },{ + validator: function (value) { + return User.find({ + _id: { $ne: this._id }, + email: value + }) + .then(users => { + if (users.length !== 0) { + throw new Error('E-mail has been used to register') + } + }) + .catch(err => { + throw err + }) + }, + message: props => `Email ${props.value} has already been used!` + }] + }, + password: { + type: String, + required: [true, 'Please input your password'], + minlength: [6, 'Please insert minimum 6 character for the password'] + } +}); + +userSchema.pre('save', function (next) { + this.email = this.email.toLowerCase() + this.password = bcrypt.hash(this.password) + next() +}) + +const User = model('User', 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 100755 index 0000000..3e5117e --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,889 @@ +{ + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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": "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" + } + }, + "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==", + "dev": true + }, + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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" + } + }, + "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" + } + }, + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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" + } + }, + "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": { + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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 100755 index 0000000..01b6df3 --- /dev/null +++ b/server/package.json @@ -0,0 +1,25 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "NODE_ENV=development nodemon app.js" + }, + "author": "Luky Winata", + "license": "ISC", + "devDependencies": { + "dotenv": "^8.2.0" + }, + "dependencies": { + "axios": "^0.19.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "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/routes/apiRoute.js b/server/routes/apiRoute.js new file mode 100755 index 0000000..5b643a5 --- /dev/null +++ b/server/routes/apiRoute.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = express.Router(); +const apiController = require('../controllers/apiController') + +router.get('/', apiController.get) + +module.exports = router; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js new file mode 100755 index 0000000..606e926 --- /dev/null +++ b/server/routes/index.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +const taskRoute = require('./taskRoute'); +const userRoute = require('./userRoute'); +const apiRoute = require('./apiRoute') + +router.use('/task', taskRoute) +router.use('/user', userRoute) +router.use('/api', apiRoute) + +module.exports = router; \ No newline at end of file diff --git a/server/routes/taskRoute.js b/server/routes/taskRoute.js new file mode 100755 index 0000000..c4dcf32 --- /dev/null +++ b/server/routes/taskRoute.js @@ -0,0 +1,16 @@ +const express = require('express'); +const router = express.Router(); +const taskController = require('../controllers/taskController'); +const authenticate = require('../middlewares/authenticate') +const authorize = require('../middlewares/authorize') + +router.use('/', authenticate) +router.post('/', taskController.add) +router.get('/', taskController.list) +router.use('/:id', authorize) +router.get('/:id', taskController.detail) +router.put('/:id', taskController.update) +router.patch('/:id', taskController.undo) +router.delete('/:id', taskController.delete) + +module.exports = router; \ No newline at end of file diff --git a/server/routes/userRoute.js b/server/routes/userRoute.js new file mode 100755 index 0000000..4f257db --- /dev/null +++ b/server/routes/userRoute.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const userController = require('../controllers/userController') +const googleAuth = require('../middlewares/googleAuth') + +router.post('/register', userController.register) +router.post('/login', userController.login) +router.post('/googleLogin', googleAuth, userController.googleLogin) + +module.exports = router; \ No newline at end of file