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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ < Back
+
+ Or, sign in with your google account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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(`
+
+ `)
+ $("#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