diff --git a/README.md b/README.md index b83fed8..6fb762e 100644 --- a/README.md +++ b/README.md @@ -1 +1,700 @@ -# FancyTodo \ No newline at end of file +# FancyTodo + +* Web:
+ https://fancy-to-do-5ee22.web.app/ +---- +List of available endpoints: +* POST /register +* POST /login +* GET /todos +* POST /todos +* GET /todos/:id +* PUT /todos/:id +* PATCH /todos/:id +* DELETE /todos/:id +* GET /weathes + +---- +**REGISTER** +---- + Register to the app + +* **URL** + + /users/register + +* **Method:** + + `POST` + +* **Data Params** + + ```javascript + { + email: "string", + password: "string" + } + ``` + +* **Success Response:** + + * **Code:** 201
+ **Content:** + ```javascript + { + id: "integer", + email: "string" + } + ``` + +* **Error Response:** + * **Code:** 400
+ **Content:** + ```javascript + { + msg: "errors" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` +* **Sample Call** +```javascript + $.ajax({ + method: 'POST', + url: `${SERVER}/users/register`, + data: { + email, + password + } + }) +``` +---- + +**LOGIN** +---- + Log into the app + +* **URL** + + /users/login + +* **Method:** + + `POST` + +* **Data Params** + + ```javascript + { + email: "string", + password: "string" + } + ``` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + access_token: "string" + } + ``` + +* **Error Response:** + * **Code:** 401
+ **Content:** + ```javascript + { + msg: "errors" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` +* **Sample Call** +```javascript + $.ajax({ + method: 'POST', + url: `${SERVER}/users/login`, + data: { + email, + password + } + }) +``` +---- + +**CREATE TODO** +---- + Returns JSON data from new todo + +* **URL** + + /todos + +* **Method:** + + `POST` + +* **Data Params** + * **Data** + ```javascript + { + title: "string", + description: "string", + due_date: date + } + ``` + * **Headers** + ```javascript + access_token = "string" + ``` + +* **Success Response:** + + * **Code:** 201
+ **Content:** + ```javascript + { + id: integer, + title: "string", + description: "string", + status: boolean + due_date: date + } + ``` +* **Error Response:** + * **Code:** 400
+ **Content:** + ```javascript + { + msg: "errors" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` + + OR + + * **Code** 401 + **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` + +* **Sample Call** +```javascript + $.ajax({ + method: 'POST', + url: `${SERVER}/todos`, + data: { + title, + description, + due_date + }, + headers: { + access_token + } + }) +``` +---- + +**READ TODO** +---- + Returns JSON data about all todos + +* **URL** + + /todos + +* **Method:** + + `GET` + +* **Data Params** + + * **Headers** + ```javascript + access_token = "string" + ``` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + [ + { + id: integer, + title: "string" + description: "string", + status: boolean, + due_date: date + }, + { + .. + } + ] + ``` + +* **Error Response:** + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: 'errors' + } + ``` + OR + + * **Code** 401 + **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` + +* **Sample Call** +```javascript + $.ajax({ + method: 'GET', + url: `${SERVER}/todos`, + headers: { + access_token: access_token + } + }) +``` +---- + +**GET A SINGLE TODO** +---- + Returns JSON data about a single todo + +* **URL** + + /todos/:id + +* **Method:** + + `GET` + +* **URL Params** + + **Required:** + ``` + id = integer + ``` + +* **Data Params** + + * **Headers** + ```javascript + access_token = "string" + ``` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + id: integer, + title: "string", + description: "string", + status: boolean + due_date: date + } + ``` + +* **Error Response:** + + * **Code:** 404
+ **Content:** + ```javascript + { + msg: "todo with id ... is not found" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: 'internal server error' + } + ``` + + OR + + * **Code** 401 + **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` + +---- + +**EDIT TODO** +---- + Returns JSON data about updated todo + +* **URL** + + /todos/:id + +* **Method:** + + `PUT` + +* **URL Params** + + **Required:** + ``` + id=integer + ``` + +* **Data Params** + + * **Data** + ```javascript + { + title: "string", + description: "string", + due_date: date + } + ``` + * **Headers** + ```javascript + access_token = "string" + ``` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + id: integer, + title: "string", + description: "string", + status: boolean, + due_date: date + } + ``` + +* **Error Response:** + + * **Code:** 404
+ **Content:** + ```javascript + { + msg: "todo with id ... is not found" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` + + OR + + * **Code** 401 + **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` +* **Sample Call** +```javascript + $.ajax({ + method: 'PUT', + url: `${SERVER}/todos/${idTemp}`, + headers: { + access_token: access_token + }, + data: { + title, + description, + due_date + } + }) +``` + +---- + +**FINISH TODO** +---- + Finish a todo + +* **URL** + + /todos/:id + +* **Method:** + + `PATCH` + +* **URL Params** + + **Required:** + ``` + id=integer + ``` + +* **Data Params** + + * **Headers** + ```javascript + access_token = "string" + ``` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + id: integer, + title: "string", + description: "string", + status: boolean + due_date: date + } + ``` + +* **Error Response:** + + * **Code:** 401
+ **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` + + OR + + * **Code:** 404
+ **Content:** + ```javascript + { + msg: 'todo is not found' + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: 'internal server error' + } + ``` +* **Sample Call** +```javascript + $.ajax({ + method: 'PATCH', + url: `${SERVER}/todos/${id}`, + headers: { + access_token: access_token + } + }) +``` + +---- + +**DELETE TODO** +---- + Returns message + +* **URL** + + /todos/:id + +* **Method:** + + `DELETE` + +* **URL Params** + + **Required:** + ``` + id=integer + ``` + +* **Data Params** + + * **Headers** + ```javascript + access_token = "string" + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + msg: 'todo deleted successfully' + } + ``` + +* **Error Response:** + + * **Code:** 404
+ **Content:** + ```javascript + { + msg: "todo is not found" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` + + OR + + * **Code** 401 + **Content:** + ```javascript + { + msg: 'authentication failed' + } + ``` + +**Sample Call** +```javascript + $.ajax({ + method: 'DELETE', + url: `${SERVER}/todos/${id}`, + headers: { + access_token: access_token + } + }) +``` +---- + +**WEATHER** +---- + Returns current weather + +* **URL** + + /weathers + +* **Method:** + + `GET` + +* **Success Response:** + + * **Code:** 200
+ **Content:** + ```javascript + { + base: "stations", + clouds: { + all: integer + }, + coord: { + lon: integer, + lat: integer + }, + main: { + temp: integer, + feels_like: integer, + temp_min: integer, + temp_max: integer, + humidity: integer, + pressure: integer + }, + weather: [ + { + id: integer, + main: "string", + description: "string", + } + ], + name: "string", + wind: { + speed: integer, + deg: integer + }, + ... + } + ``` + +* **Error Response:** + + * **Code:** 404
+ **Content:** + ```javascript + { + msg: "location is not found" + } + ``` + + OR + + * **Code:** 500
+ **Content:** + ```javascript + { + msg: "internal server error" + } + ``` + +**Sample Call** +```javascript + $.ajax({ + method: 'GET', + url: `${SERVER}/weathers` + }) +``` +---- \ No newline at end of file diff --git a/client/.firebaserc b/client/.firebaserc new file mode 100644 index 0000000..9e35220 --- /dev/null +++ b/client/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "fancy-to-do-5ee22" + } +} diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..dbb58ff --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,66 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/client/firebase.json b/client/firebase.json new file mode 100644 index 0000000..b3a5ba8 --- /dev/null +++ b/client/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": ".", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/client/functions.js b/client/functions.js new file mode 100644 index 0000000..58d282d --- /dev/null +++ b/client/functions.js @@ -0,0 +1,48 @@ +function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; + + return [year, month, day].join('-'); +} + +function convertTemp (temp) { + const celcius = (+temp-273.15).toFixed(1) + return `${celcius}°C` +} + +function source(description) { + description = description.toLowerCase() + let source + console.log(description) + switch (description) { + case "thunderstorm": + source = "https://www.flaticon.com/svg/static/icons/svg/222/222506.svg" + break; + case "mist": + source = "https://www.flaticon.com/svg/static/icons/svg/414/414927.svg" + break; + case "clouds": + source = "https://www.flaticon.com/svg/static/icons/svg/414/414927.svg" + break; + case "sunny": + source = "https://www.flaticon.com/svg/static/icons/svg/2917/2917242.svg" + break; + case "clear": + source = "https://www.flaticon.com/svg/static/icons/svg/2917/2917242.svg" + break; + case "windy": + source = "https://www.flaticon.com/svg/static/icons/svg/2917/2917242.svg" + break; + case "rain" : + source = "https://www.flaticon.com/svg/static/icons/svg/414/414974.svg" + break; + } + return source +} \ No newline at end of file diff --git a/client/index.html b/client/index.html index 69763a7..8330aca 100644 --- a/client/index.html +++ b/client/index.html @@ -1,11 +1,184 @@ + - Document + + + + + + + + + + + Fancy ToDo + - + + + + + + +
+
+

Register

+ + + +
+ + + + +
+
+

Already have an acount ?
+ Login here +

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

Login

+ + + +
+ + + +
+
+

Don't have an acount ?
+ Register here +

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

Here Are Your Todos

+
+ +

+
+
+
+ +
+ +
+
+
+
+
Add a new task
+
+
+ + +
+
+ + +
+
+ + + due date must be greater than today +


+ +

+ +
+
+
+
+ + + +
+
+
+
+
Edit task
+
+
+ + +
+
+ + +
+
+ + + due date must be greater than today +


+ +

+ +
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..d305ce3 --- /dev/null +++ b/client/index.js @@ -0,0 +1,497 @@ +const SERVER = 'https://fancy-todo-tori.herokuapp.com' + +const Toast = Swal.mixin({ + toast: true, + position: "top-end", + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.addEventListener("mouseenter", Swal.stopTimer); + toast.addEventListener("mouseleave", Swal.resumeTimer); + }, +}); + +function beforeLogin() { + $('#register').hide() + $('#nav-login').show() + $('#nav-register').show() + $('#nav-name').hide() + $('#nav-logout').hide() + $('#login').show() + $('#content').hide() + $('todos').hide() + $('#form-addToDo').hide() + $('#weather').hide() + $('#form-updateTodo').hide() +} + +function afterLogin() { + $('#register').hide() + $('#login').hide() + $('#content').show() + $('#todos').show() + $('#nav-login').hide() + $('#nav-name').empty() + $('#nav-name').append(`${localStorage.getItem('name')}`) + $('#nav-name').show() + $('#nav-register').hide() + $('#nav-logout').show() + $('#form-addToDo').hide() + $('#form-updateTodo').hide() + $('#weather').show() + $('#weather').empty() + fetchToDos() + weather() +} + +$(document).ready(_ => { + const token = localStorage.getItem('access_token') + if (token) { + afterLogin() + } else { + beforeLogin() + } +}) + +$('#nav-name').on('click', () => { + afterLogin() +}) +$('.back-btn').on('click', () => { + afterLogin() +}) + +//move to login +$('#nav-login').on('click', () => { + $('#register').hide() + $('#login').show() + $('#content').hide() +}) + +$('#login-sc').on('click', () => { + $('#register').hide() + $('#login').show() + $('#content').hide() +}) + +//move to register +$('#nav-register').on('click', () => { + $('#register').show() + $('#login').hide() + $('#content').hide() +}) + +$('#register-sc').on('click', () => { + $('#register').show() + $('#login').hide() + $('#content').hide() +}) + +//register +const register = e => { + e.preventDefault() + const email = $('#register-email').val() + const password = $('#register-pwd').val() + + $.ajax({ + method: 'POST', + url: `${SERVER}/users/register`, + data: { + email, + password + } + }) + .done(response => { + $('#register').hide() + $('#login').show() + $('#todo').hide() + + Swal.fire({ + icon: 'success', + title: 'Sucess', + text: 'Your account has been registered' + }) + }) + .fail(err => { + console.log(err) + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) + .always(() => { + $('#register-pwd').val("") + }) +} + +//login +const login = e => { + e.preventDefault() + + const email = $('#login-email').val() + const password = $('#login-pwd').val() + + $.ajax({ + method: 'POST', + url: `${SERVER}/users/login`, + data: { + email, + password + } + }) + .done(response => { + $('#nav-name').empty() + localStorage.setItem('name', email.substring(0, email.indexOf('@'))) + const access_token = response.accessToken + localStorage.setItem('access_token', access_token) + afterLogin() + + Toast.fire({ + icon: 'success', + title: 'Logged in successfully' + }) + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) + .always(() => { + $('#login-pwd').val("") + }) +} + +function onSignIn(googleUser) { + const profile = googleUser.getBasicProfile(); + const name = profile.getName() + const google_access_token = googleUser.getAuthResponse().id_token; + + $.ajax({ + method: 'POST', + url: `${SERVER}/users/googleLogin`, + data: { + google_access_token + } + }) + .done(response => { + localStorage.setItem('access_token', response.access_token) + localStorage.setItem('name',name) + $('#nav-name').empty() + afterLogin() + + Toast.fire({ + icon: 'success', + title: 'Logged in successfully' + }) + + }) + .fail(err => { + console.log(err) + }) +} + +//logout +$('#nav-logout').on('click', () => { + Swal.fire({ + title: `Logout`, + text: `You're going to be logged out`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Logout' + }) + .then((result) => { + if (result.isConfirmed) { + Swal.fire({ + icon: 'info', + title: 'Successfully logged out' + }) + beforeLogin() + localStorage.clear() + signOut() + + } else { + Toast.fire({ + icon: 'info', + title: 'cancelled' + }) + afterLogin() + } + }) + +}) + +function signOut() { + const auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(function () { + console.log('User signed out.'); + }) +}; + +//Weather +const weather = () => { + $('#weather').empty() + $.ajax({ + method: 'GET', + url: `${SERVER}/weathers` + }) + .done(response => { + console.log(response) + $('#weather').append(` +
Current Weather
+

Place +
${response.name}

+ +

Description +
${response.weather[0].main} + weather-icon

+ +

Temperature +
Temperature: ${response.main.temp.toFixed(1)}°C +
Feels Like: ${response.main.feels_like.toFixed(1)}°C

+ +

Humidity +
${response.main.humidity}%

+ `) + }) + .fail(err => { + Toast.fire({ + icon: 'warning', + title: `i can't find your location` + }) + $('#weather').hide() + }) +} + +//fetchTodos +const fetchToDos = () => { + const access_token = localStorage.getItem('access_token') + $('#todos').empty() + $.ajax({ + method: 'GET', + url: `${SERVER}/todos`, + headers: { + access_token: access_token + } + }) + .done(response => { + if (response[0]) { + response.forEach(el => { + if (el.status === false) { + $('#todos').append(` +
+
+
${el.title}
+
+ ${el.description}

+ Due at: ${formatDate(el.due_date)} +
+
+

+ Finish Task
+ Update Task + Delete Task +

+
+
`) + } else { + $('#todos').append(` +
+
+
${el.title}
+
+ ${el.description}

+ Due at: ${formatDate(el.due_date)} +
+
+

+ Task Finished
+ Delete Task +

+
+
`) + } + }) + } else { + $('#todos').append(` +
There's nothing here
Start adding some task
`) + } + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: err.responseJSON.msg + }) + }) +} + +//addTodo +const addTodo = () => { + const access_token = localStorage.getItem('access_token') + const title = $('#add_title').val() + const description = $('#add_description').val() + const due_date = $('#add_due_date').val() + + $.ajax({ + method: 'POST', + url: `${SERVER}/todos`, + data: { + title, + description, + due_date + }, + headers: { + access_token + } + }) + .done(response => { + afterLogin() + Toast.fire({ + icon: 'success', + title: 'ToDo added successfully' + }) + + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) + .always(() => { + $('#add_title').val("") + $('#add_description').val("") + $('#add_due_date').val("") + }) +} + +$('#addTodo').on('click', () => { + $('#todos').hide() + $('#form-addToDo').show() + $('#form-updateTodo').hide() +}) + +//updateTodo +let idTemp; + +const updateToDo = _ => { + const access_token = localStorage.getItem('access_token') + const title = $('#edit_title').val() + const description = $('#edit_description').val() + const due_date = $('#edit_due_date').val() + + $.ajax({ + method: 'PUT', + url: `${SERVER}/todos/${idTemp}`, + headers: { + access_token: access_token + }, + data: { + title, + description, + due_date + } + }) + .done(response => { + afterLogin() + Toast.fire({ + icon: 'success', + title: `${response.title} has been updated` + }) + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) +} + +const updateToDoForm = (id, title, description, due_date) => { + afterLogin() + $('#form-updateTodo').show() + $('#edit_title').val(title) + $('#edit_description').val(description) + $('#edit_due_date').val(formatDate(due_date)) + idTemp = id + +} + +//finishTodo +const finishToDo = id => { + const access_token = localStorage.getItem('access_token') + $.ajax({ + method: 'PATCH', + url: `${SERVER}/todos/${id}`, + headers: { + access_token: access_token + } + }) + .done(response => { + afterLogin() + Toast.fire({ + icon: 'success', + title: `you have finished todo ${response.title}` + }) + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) +} + +//deleteToDo +const deleteToDo = id => { + const access_token = localStorage.getItem('access_token') + + Swal.fire({ + title: `Delete Task`, + text: `You're going to delete this task`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Delete' + }) + .then((result) => { + if (result.isConfirmed) { + $.ajax({ + method: 'DELETE', + url: `${SERVER}/todos/${id}`, + headers: { + access_token: access_token + } + }) + .done(response => { + afterLogin() + Toast.fire({ + icon: 'success', + title: 'todo successfully deleted' + }) + }) + .fail(err => { + Swal.fire({ + icon: 'error', + title: 'Error', + text: err.responseJSON.msg + }) + }) + + } else { + Toast.fire({ + icon: 'info', + title: 'cancelled' + }) + afterLogin() + } + }) +} \ No newline at end of file diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..954ab4c --- /dev/null +++ b/client/style.css @@ -0,0 +1,45 @@ +html, +body { + height: 100%; +} + +body { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding-top: 40px; + padding-bottom: 40px; + background-color: #f5f5f5; + font-family: 'Prompt', sans-serif; +} + +.form-signin { + width: 100%; + max-width: 330px; + padding: 15px; + margin: auto; +} +.form-signin .checkbox { + font-weight: 400; +} +.form-signin .form-control { + position: relative; + box-sizing: border-box; + height: auto; + padding: 10px; + font-size: 16px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="email"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..1dcef2d --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env \ No newline at end of file diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..147ee06 --- /dev/null +++ b/server/app.js @@ -0,0 +1,19 @@ +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config() +} + +const express = require('express') +const cors = require('cors') +const router = require('./routes') +const error_handler = require('./middlewares/error_handler') + +const app = express() +const port = process.env.PORT || 3000 + +app.use(cors()) +app.use(express.json()) +app.use(express.urlencoded({extended: true})) +app.use(router) +app.use(error_handler) + +app.listen(port, () => console.log(`listening at ${port}`)) \ No newline at end of file diff --git a/server/config/config.json b/server/config/config.json new file mode 100644 index 0000000..eedd1d5 --- /dev/null +++ b/server/config/config.json @@ -0,0 +1,20 @@ +{ + "development": { + "username": "postgres", + "password": "torian05092002", + "database": "fancytodo", + "host": "127.0.0.1", + "dialect": "postgres" + }, + "test": { + "username": "root", + "password": null, + "database": "database_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "use_env_variable": "DATABASE_URL", + "dialect": "postgres" + } +} diff --git a/server/controllers/todoController.js b/server/controllers/todoController.js new file mode 100644 index 0000000..7b3ca28 --- /dev/null +++ b/server/controllers/todoController.js @@ -0,0 +1,111 @@ +const {ToDo} = require('../models/index') + +class ToDoController { + static async create(req, res, next) { + try { + const payload = { + title: req.body.title, + description: req.body.description, + due_date: req.body.due_date, + UserId: req.user.id + } + const newToDo = await ToDo.create(payload, { + returning: true + }) + res.status(201).json(newToDo) + } catch (err) { + next(err) + } + } + + static async read(req, res, next) { + try { + const UserId = req.user.id + const todos = await ToDo.findAll({ + order: [['id', 'asc']], + where: { + UserId: UserId + } + }) + res.status(200).json(todos) + } catch (err) { + next(err) + } + } + + static async findOne(req, res, next) { + try { + const id = +req.params.id + const todo = await ToDo.findByPk(id) + if (todo) { + res.status(200).json(todo) + } else { + throw { msg: `todo with id ${id} is not found`, status: 404 } + } + } catch (err) { + next(err) + } + } + + static async update(req, res, next) { + try { + const payload = { + title: req.body.title, + description: req.body.description, + due_date: req.body.due_date, + } + const updated = await ToDo.update(payload, { + where: { + id: +req.params.id + }, + returning: true + }) + if (updated[0] !== 1) { + throw { msg: `todo with id ${+req.params.id} is not found`, status: 404 } + } else { + res.status(200).json(updated[1][0]) + } + + } catch (err) { + next(err) + } + } + + static async finish(req, res, next) { + try { + const finished = await ToDo.update({status: true}, { + where: { + id: +req.params.id + }, + returning: true + }) + if (finished[0] !== 1) { + throw { msg: `todo with id ${+req.params.id} is not found`, status: 404 } + } else { + res.status(200).json(finished[1][0]) + } + } catch (err) { + next(err) + } + } + + static async delete(req, res, next) { + try { + const destroyed = await ToDo.destroy({ + where: { + id: +req.params.id + } + }) + if (destroyed !== 1) { + throw { msg: `todo with id ${+req.params.id} is not found`, status: 404 } + } else { + res.status(200).json({msg: `todo deleted successfuly`}) + } + } catch (err) { + next(err) + } + + } +} + +module.exports = ToDoController \ No newline at end of file diff --git a/server/controllers/userController.js b/server/controllers/userController.js new file mode 100644 index 0000000..2691cc0 --- /dev/null +++ b/server/controllers/userController.js @@ -0,0 +1,97 @@ +const { User } = require('../models/index') +const { compare } = require('../helper/bcrypt') +const { signToken } = require('../helper/jwt') +const {OAuth2Client} = require('google-auth-library'); + +class UserController { + static async register(req, res, next) { + try { + const payload = { + email: req.body.email, + password: req.body.password + } + + const user = await User.create(payload) + res.status(201).json({ + id: user.id, + email: user.email + }) + } catch (err) { + next(err) + } + } + + static async login(req, res, next) { + try { + const payload = { + email: req.body.email, + password: req.body.password + } + + const user = await User.findOne({ + where: { + email: payload.email + } + }) + + if (!user) { + throw { msg: 'username or password is incorrect', status: 401 } + } else if (!compare(payload.password, user.password)) { + throw { msg: 'username or password is incorrect', status: 401 } + } else { + const payload = { + id: user.id, + email:user.email + } + const token = signToken(payload) + res.status(200).json({accessToken: token}) + } + + } catch (err) { + next(err) + } + } + + static googleLogin (req, res, next) { + const {google_access_token} = req.body + const client = new OAuth2Client(process.env.CLIENT_ID); + + let email + client.verifyIdToken({ + idToken: google_access_token, + audience: process.env.CLIENT_ID + }) + .then(ticket => { + const payload = ticket.getPayload() + email = payload.email + return User.findOne({ + where: { + email: payload.email + } + }) + }) + .then(user => { + if (user) { + return user + }else { + const obj = { + email: email, + password: 'incorrect329' + } + return User.create(obj) + } + }) + .then(data => { + const access_token = signToken({ + id: data.id, + email: data.email + }) + return res.status(200).json({access_token}) + }) + .catch(err => { + next(err) + }) + } +} + +module.exports = UserController \ No newline at end of file diff --git a/server/controllers/weatherController.js b/server/controllers/weatherController.js new file mode 100644 index 0000000..e5bbb46 --- /dev/null +++ b/server/controllers/weatherController.js @@ -0,0 +1,31 @@ +const axios = require('axios') + +class WeatherController { + static currentWeather (req, res, next) { + axios({ + method: 'GET', + url: 'https://api.ipgeolocation.io/getip' + }) + .then(response => { + const ip = response.data.ip + return axios({ + method: 'GET', + url: `https://api.ipgeolocation.io/astronomy?apiKey=${process.env.GEO}&ip=${ip}` + }) + }) + .then(result => { + const city = result.data.location.state_prov + return axios({ + url: `http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER}&units=metric`, + method: 'GET' + }) + }) + .then(data => { + res.status(200).json(data.data) + }) + .catch(err => { + next(err) + }) + } +} +module.exports = WeatherController \ No newline at end of file diff --git a/server/helper/bcrypt.js b/server/helper/bcrypt.js new file mode 100644 index 0000000..0db51e1 --- /dev/null +++ b/server/helper/bcrypt.js @@ -0,0 +1,16 @@ +const bcrypt = require("bcryptjs") + +const hashPassword = password => { + const hashed = bcrypt.hashSync(password, +process.env.SALT) + return hashed +} + +const compare = (password, hashed) => { + const result = bcrypt.compareSync(password, hashed) + return result +} + +module.exports = { + hashPassword, + compare +} \ No newline at end of file diff --git a/server/helper/jwt.js b/server/helper/jwt.js new file mode 100644 index 0000000..e9d9253 --- /dev/null +++ b/server/helper/jwt.js @@ -0,0 +1,16 @@ +const jwt = require('jsonwebtoken') + +const signToken = payload => { + const token = jwt.sign(payload, process.env.SECRET) + return token +} + +const verifyToken = token => { + const decoded = jwt.verify(token, process.env.SECRET) + return decoded +} + +module.exports = { + signToken, + verifyToken +} \ No newline at end of file diff --git a/server/middlewares/authentication.js b/server/middlewares/authentication.js new file mode 100644 index 0000000..2a17710 --- /dev/null +++ b/server/middlewares/authentication.js @@ -0,0 +1,28 @@ +const { verifyToken } = require('../helper/jwt') +const { User } = require('../models') + +const authenticate = async (req, res, next) => { + const {access_token} = req.headers + try { + if (!access_token) { + throw { msg: `Authentication failed`, status: 401 } + } else { + const decoded = verifyToken(access_token) + const user = await User.findOne({ + where: { + email: decoded.email + } + }) + if (!user) { + throw { msg: `Authentication failed`, status: 401 } + } else { + req.user = decoded + next() + } + } + } catch (err) { + next(err) + } +} + +module.exports = authenticate \ No newline at end of file diff --git a/server/middlewares/authorization.js b/server/middlewares/authorization.js new file mode 100644 index 0000000..293810b --- /dev/null +++ b/server/middlewares/authorization.js @@ -0,0 +1,19 @@ +const { ToDo } = require('../models') + +const authorization = async (req, res, next) => { + const id = +req.params.id + try { + const todo = await ToDo.findByPk(id) + if (!todo) { + throw { msg: `Todo is not found`, status: 404 } + } else if (todo.UserId !== req.user.id) { + throw { msg: `Not Authorized`, status: 401 } + } else { + next() + } + } catch (err) { + next(err) + } +} + +module.exports = authorization \ No newline at end of file diff --git a/server/middlewares/error_handler.js b/server/middlewares/error_handler.js new file mode 100644 index 0000000..d55fb63 --- /dev/null +++ b/server/middlewares/error_handler.js @@ -0,0 +1,11 @@ +module.exports = function (err, req, res, next) { + let status = err.status || 500 + let msg = err.msg || 'Internal Server Error' + + if (err.name === 'SequelizeValidationError' || err.name === 'SequelizeUniqueConstraintError'){ + status = 400 + msg = err.errors.map(el => el.message).join(', ') + } + console.log(err) + res.status(status).json({msg}) +} \ No newline at end of file diff --git a/server/migrations/2020102605111-create-user.js b/server/migrations/2020102605111-create-user.js new file mode 100644 index 0000000..1a1f21a --- /dev/null +++ b/server/migrations/2020102605111-create-user.js @@ -0,0 +1,29 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + email: { + unique: true, + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + createdAt: { + type: Sequelize.DATE + }, + updatedAt: { + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Users'); + } +}; \ No newline at end of file diff --git a/server/migrations/20201026060655-create-to-do.js b/server/migrations/20201026060655-create-to-do.js new file mode 100644 index 0000000..46ab270 --- /dev/null +++ b/server/migrations/20201026060655-create-to-do.js @@ -0,0 +1,46 @@ +'use strict'; + +const { sequelize } = require("../models"); + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('ToDos', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + title: { + type: Sequelize.STRING + }, + description: { + type: Sequelize.STRING + }, + status: { + type: Sequelize.BOOLEAN + }, + due_date: { + type: Sequelize.DATE + }, + UserId : { + type: Sequelize.INTEGER, + references: { + model: 'Users', + key: 'id' + }, + onDelete: 'cascade', + onUpdate: 'cascade' + }, + createdAt: { + type: Sequelize.DATE + }, + updatedAt: { + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('ToDos'); + } +}; \ No newline at end of file diff --git a/server/models/index.js b/server/models/index.js new file mode 100644 index 0000000..33f09e7 --- /dev/null +++ b/server/models/index.js @@ -0,0 +1,37 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Sequelize = require('sequelize'); +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; +const config = require(__dirname + '/../config/config.json')[env]; +const db = {}; + +let sequelize; +if (config.use_env_variable) { + sequelize = new Sequelize(process.env[config.use_env_variable], config); +} else { + sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +fs + .readdirSync(__dirname) + .filter(file => { + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + }) + .forEach(file => { + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); + db[model.name] = model; + }); + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; diff --git a/server/models/todo.js b/server/models/todo.js new file mode 100644 index 0000000..40c1d9c --- /dev/null +++ b/server/models/todo.js @@ -0,0 +1,93 @@ +'use strict'; +const {Model} = require('sequelize'); + +module.exports = (sequelize, DataTypes) => { + class ToDo extends Model { + + static associate(models) { + ToDo.belongsTo(models.User, { + foreignKey: 'UserId', + targetKey: 'id' + }) + } + }; + ToDo.init({ + title: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + args: true, + msg: `title can't be empty` + }, + notEmpty: { + args: true, + msg: `title can't be empty` + } + } + }, + description: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notEmpty: { + args: true, + msg: `description can't be empty` + }, + notNull: { + args: true, + msg: `description can't be empty` + } + } + }, + status: { + type: DataTypes.BOOLEAN, + allowNull: false, + validate: { + notEmpty: { + args: true, + msg: `status can't be empty` + }, + notNull: { + args: true, + msg: `status can't be empty` + } + } + }, + due_date: { + type: DataTypes.DATE, + allowNull: false, + validate: { + notEmpty : { + args: true, + msg: `due date can't be empty` + }, + notNull: { + args: true, + msg: `due date can't be empty` + }, + isDate: { + args: true, + msg: `must be in date format` + }, + gtToday(value) { + const now = new Date() + if (now >= value) { + throw new Error('due date should be greater than today') + } + } + } + } + }, { + sequelize, + modelName: 'ToDo', + }); + + ToDo.addHook('beforeValidate', (instance, options) => { + if (!instance.status) { + instance.status = false + } + }) + + return ToDo; +}; \ No newline at end of file diff --git a/server/models/user.js b/server/models/user.js new file mode 100644 index 0000000..88b3d7c --- /dev/null +++ b/server/models/user.js @@ -0,0 +1,63 @@ +'use strict'; +const {Model} = require('sequelize'); +const { hashPassword } = require('../helper/bcrypt') + +module.exports = (sequelize, DataTypes) => { + class User extends Model { + + static associate(models) { + User.hasMany(models.ToDo) + } + }; + User.init({ + email: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isEmail: { + args: true, + msg: 'please insert a valid email' + }, + notEmpty: { + args: true, + msg: `email can't be empty` + }, + notNull: { + args: true, + msg: `email can't be empty` + } + } + }, + password: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + args: true, + msg: `password can't be empty` + }, + notEmpty: { + args: true, + msg: `password can't be empty` + }, + isAlphanumeric: { + args: true, + msg: `password must be alphanumeric` + }, + len: { + args: [6,20], + msg: `password must be a minimal of 6 and a maximum of 20 characters` + } + } + } + }, { + sequelize, + modelName: 'User', + }); + + User.addHook('beforeCreate', (instance, options) => { + instance.password = hashPassword(instance.password) + }) + + return User; +}; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..bc6ecf6 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1024 @@ +{ + "name": "fancytodo", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", + "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" + }, + "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": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "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.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "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==" + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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=" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "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" + } + }, + "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 + }, + "dottie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" + }, + "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=" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + }, + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.0.1.tgz", + "integrity": "sha512-jOin8xRZ/UytQeBpSXFqIzqU7Fi5TqgPNLlUsSB8kjJ76+FiGBfImF8KJu++c6J4jOldfJUtt0YmkRj2ZpSHTQ==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", + "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + }, + "dependencies": { + "gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + } + } + }, + "google-auth-library": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", + "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.5.tgz", + "integrity": "sha512-wvjkecutFh8kVfbcdBdUWqDRrXb+WrgD79DBDEYf1Om8S1FluhylhtFjrL7Tx69vNhh259qA3Q1P4sPtb+kUYw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, + "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" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "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" + } + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "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": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.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" + } + }, + "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" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "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.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "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" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "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=" + }, + "pg": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.4.2.tgz", + "integrity": "sha512-E9FlUrrc7w3+sbRmL1CSw99vifACzB2TjhMM9J5w9D1LIg+6un0jKkpHS1EQf2CWhKhec2bhrBLVMmUBDbjPRQ==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.4.0", + "pg-pool": "^3.2.2", + "pg-protocol": "^1.3.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", + "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", + "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==" + }, + "pg-protocol": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.3.0.tgz", + "integrity": "sha512-64/bYByMrhWULUaCd+6/72c9PMWhiVFs3EVxl9Ct6a3v/U8+rKgqP2w+kKg/BIGgMJyB+Bk/eNivT32Al+Jghw==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "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" + } + }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, + "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==" + }, + "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==" + } + } + }, + "sequelize": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", + "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", + "requires": { + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.15", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "retry-as-promised": "^3.2.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", + "toposort-class": "^1.0.1", + "uuid": "^8.1.0", + "validator": "^10.11.0", + "wkx": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, + "sequelize-pool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", + "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==" + }, + "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==" + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "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=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "requires": { + "@types/node": "*" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..3677c9d --- /dev/null +++ b/server/package.json @@ -0,0 +1,27 @@ +{ + "name": "fancytodo", + "version": "1.0.0", + "description": "ToDo App", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon app.js", + "start": "node app.js" + }, + "keywords": [], + "author": "torianyap", + "license": "ISC", + "dependencies": { + "axios": "^0.21.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "google-auth-library": "^6.1.3", + "jsonwebtoken": "^8.5.1", + "pg": "^8.4.2", + "sequelize": "^6.3.5" + }, + "devDependencies": { + "dotenv": "^8.2.0" + } +} diff --git a/server/readme.md b/server/readme.md deleted file mode 100644 index 3aadcc2..0000000 --- a/server/readme.md +++ /dev/null @@ -1 +0,0 @@ -# FancyTodo Server \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js new file mode 100644 index 0000000..d7737c4 --- /dev/null +++ b/server/routes/index.js @@ -0,0 +1,10 @@ +const route = require('express').Router() +const todoRoute = require('./todoRoute') +const userRoute = require('./userRoute') +const weatherRoute = require('./weatherRoute') + +route.use('/todos', todoRoute) +route.use('/users', userRoute) +route.use('/weathers', weatherRoute) + +module.exports = route \ No newline at end of file diff --git a/server/routes/todoRoute.js b/server/routes/todoRoute.js new file mode 100644 index 0000000..e4cb43e --- /dev/null +++ b/server/routes/todoRoute.js @@ -0,0 +1,18 @@ +const route = require('express').Router() +const ToDoController = require('../controllers/todoController') +const authenticate = require('../middlewares/authentication') +const authorization = require('../middlewares/authorization') + +route.use(authenticate) + +route.post('/', ToDoController.create) +route.get('/', ToDoController.read) + +route.use('/:id', authorization) + +route.get('/:id', ToDoController.findOne) +route.put('/:id', ToDoController.update) +route.patch('/:id', ToDoController.finish) +route.delete('/:id', ToDoController.delete) + +module.exports = route \ No newline at end of file diff --git a/server/routes/userRoute.js b/server/routes/userRoute.js new file mode 100644 index 0000000..3ad946c --- /dev/null +++ b/server/routes/userRoute.js @@ -0,0 +1,8 @@ +const route = require('express').Router() +const UserController = require('../controllers/userController') + +route.post('/register', UserController.register) +route.post('/login', UserController.login) +route.post('/googleLogin', UserController.googleLogin) + +module.exports = route \ No newline at end of file diff --git a/server/routes/weatherRoute.js b/server/routes/weatherRoute.js new file mode 100644 index 0000000..2877a81 --- /dev/null +++ b/server/routes/weatherRoute.js @@ -0,0 +1,6 @@ +const route = require('express').Router() +const WeatherController = require('../controllers/weatherController') + +route.get('/', WeatherController.currentWeather) + +module.exports = route \ No newline at end of file