From 74ab645550f571aa022db3091c035812c94cd7d5 Mon Sep 17 00:00:00 2001 From: Dusan Stevanovic Date: Fri, 6 Jan 2023 14:23:39 +0100 Subject: [PATCH] Ditch Netlify, add Express, refactor --- .gitignore | 2 - app.js | 21 + functions/login-api.js | 110 --- functions/profile-put-api.js | 186 ---- functions/signup-api.js | 166 ---- functions/verify-put-api.js | 121 --- index.js | 513 ++++++++--- netlify.toml | 16 - package-lock.json | 1055 ++++++++++++++++++++--- package.json | 9 +- readme.md | 6 +- server/index.js | 7 + server/login-api.js | 79 ++ server/profile-put-api.js | 148 ++++ server/signup-api.js | 138 +++ server/socket-api.js | 9 + {functions => server}/tournament-api.js | 0 server/verify-put-api.js | 101 +++ src/Chat.elm | 107 +++ src/Credentials.elm | 132 ++- src/Login.elm | 2 +- src/Main.elm | 89 +- src/Profile.elm | 2 +- src/Signup.elm | 2 +- src/Verification.elm | 2 +- 25 files changed, 2191 insertions(+), 832 deletions(-) create mode 100644 app.js delete mode 100644 functions/login-api.js delete mode 100644 functions/profile-put-api.js delete mode 100644 functions/signup-api.js delete mode 100644 functions/verify-put-api.js delete mode 100755 netlify.toml create mode 100644 server/index.js create mode 100644 server/login-api.js create mode 100644 server/profile-put-api.js create mode 100644 server/signup-api.js create mode 100644 server/socket-api.js rename {functions => server}/tournament-api.js (100%) create mode 100644 server/verify-put-api.js create mode 100644 src/Chat.elm diff --git a/.gitignore b/.gitignore index 6272634..b90905f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# Local Netlify folder -.netlify node_modules elm-stuff .cache diff --git a/app.js b/app.js new file mode 100644 index 0000000..07fe4f3 --- /dev/null +++ b/app.js @@ -0,0 +1,21 @@ +require('dotenv').config(); +const express = require('express'); +const path = require('path'); +const bodyParser = require('body-parser'); +const { routes } = require('./server'); + +const PORT = process.env.PORT || 8080; + +const app = express(); + +app.use(express.json()); + +routes.forEach((route) => { + route = route.module; + + app[route.method](route.path, route.handler); +}); + +app.listen(PORT, () => { + console.log(`Server is listening on port ${PORT}`); +}); diff --git a/functions/login-api.js b/functions/login-api.js deleted file mode 100644 index b4d3f7d..0000000 --- a/functions/login-api.js +++ /dev/null @@ -1,110 +0,0 @@ -// const { createClient } = require('@astrajs/collections'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); -const { v4: uuid } = require('uuid'); -const { clientPromise } = require('../connect-database'); - -exports.handler = async function (req) { - const { body, httpMethod } = req; - let client; - let parsedBody; - - try { - client = await clientPromise; - } catch (err) { - console.log('Client: ', err); - client = err.toString(); - } - - if (httpMethod !== 'POST') - return { - statusCode: 403, - }; - - if (!client || typeof client?.execute !== 'function') { - return { - statusCode: 500, - body: `{message: There is no client, payload: ${client}}`, - }; - } - - try { - parsedBody = JSON.parse(body); - } catch (error) { - return { - statusCode: 500, - body: error.toString(error), - }; - } - - const { email, password } = parsedBody; - // const astraClient = await createClient({ - // astraDatabaseId: process.env.ASTRA_DB_ID, - // astraDatabaseRegion: process.env.ASTRA_DB_REGION, - // applicationToken: process.env.ASTRA_DB_APPLICATION_TOKEN, - // }); - - // const usersCollection = astraClient - // .namespace(process.env.ASTRA_DB_KEYSPACE) - // .collection('users'); - - // const user = await usersCollection.findOne({ email: { $eq: email } }); - const findUser = async (parameters) => { - const query = `SELECT * FROM ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users WHERE email = ? ALLOW FILTERING;`; - try { - const result = client.execute(query, parameters, { prepare: true }); - - return result; - } catch (ex) { - console.log('Error in finduser', ex.toString()); - } - }; - const user = await findUser([email]); - - if (!user?.rows[0]) { - return { - statusCode: 401, - }; - } - - const { id, isverified, passwordhash, salt, firstname, verificationstring, avatarurl } = - user.rows[0]; - const pepper = process.env.PEPPER_STRING; - - const isCorrect = await bcrypt.compare(salt + password + pepper, passwordhash); - - if (isCorrect) { - const token = jwt.sign( - { - id, - isverified, - email, - firstname, - verificationstring, - profilepicurl: avatarurl || '', - }, - process.env.JWT_SECRET, - { - expiresIn: '2h', - }, - ); - - if (!token) - return { - statusCode: 403, - }; - - return { - statusCode: 200, - body: JSON.stringify({ token }), - }; - } else { - return { - statusCode: 401, - }; - } -}; diff --git a/functions/profile-put-api.js b/functions/profile-put-api.js deleted file mode 100644 index 720cdcd..0000000 --- a/functions/profile-put-api.js +++ /dev/null @@ -1,186 +0,0 @@ -// const { createClient } = require('@astrajs/collections'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); -const { v4: uuid } = require('uuid'); -const { clientPromise } = require('../connect-database'); -const querystring = require('querystring').escape; -const initializeApp = require('firebase/app').initializeApp; -const { - getStorage, - ref, - uploadBytes, - getDownloadURL, - getStream, - uploadString, -} = require('firebase/storage'); - -exports.handler = async function (req) { - const { body, httpMethod } = req; - const { authorization } = req.headers; - const client = await clientPromise; - let parsedBody; - - if (httpMethod !== 'PUT') - return { - statusCode: 403, - }; - - if (!client) - return { - statusCode: 500, - body: `There is no client: ${client.toString()}`, - }; - - try { - parsedBody = JSON.parse(body); - } catch (error) { - return { - statusCode: 500, - body: error.toString(error), - }; - } - - // const astraClient = await createClient({ - // astraDatabaseId: process.env.ASTRA_DB_ID, - // astraDatabaseRegion: process.env.ASTRA_DB_REGION, - // applicationToken: process.env.ASTRA_DB_APPLICATION_TOKEN, - // }); - - // const usersCollection = astraClient - // .namespace(process.env.ASTRA_DB_KEYSPACE) - // .collection('users'); - - if (!authorization) { - return { - statusCode: 401, - body: 'No authorization header sent', - }; - } - const { email, firstname, imagefile } = parsedBody; - const token = authorization.split(' ')[1]; - // const user = await usersCollection.findOne({ email: { $eq: email } }); - - let decodedToken; - - try { - decodedToken = await jwt.verify(token, process.env.JWT_SECRET); - } catch (err) { - return { - statusCode: 401, - body: err.toString(), - }; - } - - const firebaseConfig = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, - measurementId: process.env.FIREBASE_MEASUREMENT_ID, - }; - - const app = initializeApp(firebaseConfig); - const storage = getStorage(app); - - const { id } = decodedToken; - - let photoUrl = ''; - - try { - if (imagefile.length > 0) { - await uploadString( - ref(storage, `/images/profile-pic-${id}.jpeg`), - imagefile, - 'data_url', - ); - const downloadUrl = await getDownloadURL( - ref(storage, `/images/profile-pic-${id}.jpeg`), - ); - photoUrl = - downloadUrl.length > 0 - ? `https://firebasestorage.googleapis.com/v0/b/${ - process.env.FIREBASE_STORAGE_BUCKET - }/o/${querystring(`images/profile-pic-${id}.jpeg`)}?alt=media` - : ''; - } - } catch (err) { - return { - statusCode: 403, - }; - } - - const updateUser = async (parameters) => { - const query = `UPDATE ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users SET firstname = ?, avatarurl = ? WHERE id = ? IF EXISTS;`; - try { - const result = await client.execute(query, parameters, { prepare: true }); - // result would be undefined but query would be executed and entery is written in the DB - return result; - } catch (ex) { - console.log('Error in createUser', ex.toString()); - } - }; - - const { - rows: [applied], - } = await updateUser([firstname, photoUrl, id]); - - if (!applied['[applied]']) { - console.log('Unsuccessfull in updating user'); - return { - statusCode: 404, - }; - } - - const findUser = async (parameters) => { - const query = `SELECT * FROM ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users WHERE email = ? ALLOW FILTERING;`; - try { - const result = await client.execute(query, parameters, { prepare: true }); - - return result; - } catch (ex) { - console.log('Error in finduser', ex.toString()); - } - }; - const user = await findUser([email]); - const { - id: decodedId, - isverified: isVerified, - email: emailFromUser, - firstname: firstName, - verificationstring, - avatarurl, - } = user.rows[0]; - - const newToken = jwt.sign( - { - id: decodedId, - isverified: isVerified, - email: emailFromUser, - firstname: firstName, - verificationstring, - profilepicurl: avatarurl || '', - }, - process.env.JWT_SECRET, - { expiresIn: '2h' }, - ); - - if (!newToken) - return { - statusCode: 403, - }; - - return { - statusCode: 200, - body: JSON.stringify({ token: newToken }), - }; -}; diff --git a/functions/signup-api.js b/functions/signup-api.js deleted file mode 100644 index 9723a28..0000000 --- a/functions/signup-api.js +++ /dev/null @@ -1,166 +0,0 @@ -// const { createClient } = require('@astrajs/collections'); -const cassandra = require('cassandra-driver'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); -const { v4: uuid } = require('uuid'); -const { clientPromise } = require('../connect-database'); -const sendgrid = require('@sendgrid/mail'); - -sendgrid.setApiKey(process.env.SENDGRID_API_KEY); - -const sendEmail = ({ to, from, subject, text, html = '' }) => { - const msg = { to, from, subject, text, html }; - return sendgrid.send(msg); -}; - -exports.handler = async function (req, context) { - const { body, httpMethod } = req; - let client; - let parsedBody; - - try { - client = await clientPromise; - } catch (err) { - console.log('Client: ', err); - client = err.toString(); - } - - if (httpMethod !== 'POST') - return { - statusCode: 403, - }; - - if (!client || typeof client?.execute !== 'function') { - return { - statusCode: 500, - body: `{message: There is no client, payload: ${client}}`, - }; - } - - try { - parsedBody = JSON.parse(body); - } catch (error) { - return { - statusCode: 500, - body: error.toString(error), - }; - } - - const { email, password } = parsedBody; - // const astraClient = await createClient({ - // astraDatabaseId: process.env.ASTRA_DB_ID, - // astraDatabaseRegion: process.env.ASTRA_DB_REGION, - // applicationToken: process.env.ASTRA_DB_APPLICATION_TOKEN, - // }); - - // const usersCollection = astraClient - // .namespace(process.env.ASTRA_DB_KEYSPACE) - // .collection('users'); - const findUser = async (parameters) => { - const query = `SELECT * FROM ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users WHERE email = ? ALLOW FILTERING;`; - try { - const result = client.execute(query, parameters, { prepare: true }); - return result; - } catch (ex) { - console.log('Error in finduser', ex.toString()); - } - }; - - const user = await findUser([email]); - - if (user?.rows[0]) { - // conflict error code - return { - statusCode: 409, - }; - } - const salt = uuid(); - const pepper = process.env.PEPPER_STRING; - const passwordHash = await bcrypt.hash(salt + password + pepper, 10); // 2 arg is num of iterations to - const verificationString = uuid(); - const createdId = uuid(); - - const createUser = async (parameters) => { - const query = `INSERT INTO ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users (id, email, firstname, isadmin, isverified, lastname, passwordhash, salt, verificationstring, avatarurl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`; - try { - const result = await client.execute(query, parameters, { prepare: true }); - // result would be undefined but query would be executed and entery is wirtten in the DB - return result; - } catch (ex) { - console.log('Error in createUser', ex.toString()); - } - }; - - const createdUser = await createUser([ - createdId, - email, - '', - false, - false, - '', - passwordHash, - salt, - verificationString, - '', - ]); - - try { - await sendEmail({ - to: email, - from: 'dooshanstevanovic@gmail.com', - subject: 'Please verify your email', - text: `Thanks for signin up ! To verify your email click here: ${ - process.env.NODE_ENV === 'development' - ? 'http://localhost:8888' - : 'https://my-elm-app.netlify.app' - }/verify-email/${verificationString}`, - html: `
-

Hello !

-
-

Thanks for signin up !

-

To verify your email click here

-
-
`, - }); - } catch (err) { - console.log('Error in signUp', err.response.body.errors); - return { - statusCode: 500, - }; - } - - const token = jwt.sign( - { - id: createdId, - isverified: false, - email, - firstname: '', - verificationstring: verificationString, - profilepicurl: '', - }, - process.env.JWT_SECRET, - { expiresIn: '2h' }, - ); - - if (!token) - return { - statusCode: 403, - }; - - return { - statusCode: 200, - body: JSON.stringify({ token }), - }; -}; diff --git a/functions/verify-put-api.js b/functions/verify-put-api.js deleted file mode 100644 index 627b479..0000000 --- a/functions/verify-put-api.js +++ /dev/null @@ -1,121 +0,0 @@ -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); -const { v4: uuid } = require('uuid'); -const { clientPromise } = require('../connect-database'); - -exports.handler = async function (req) { - const { httpMethod } = req; - const { authorization } = req.headers; - const client = await clientPromise; - - if (httpMethod !== 'PUT') - return { - statusCode: 403, - }; - - if (!client) - return { - statusCode: 500, - body: `There is no client: ${client.toString()}`, - }; - - if (!authorization) { - return { - statusCode: 401, - body: 'No authorization header sent', - }; - } - - const token = authorization.split(' ')[1]; - - let decodedToken; - - try { - decodedToken = await jwt.verify(token, process.env.JWT_SECRET); - } catch (err) { - return { - statusCode: 401, - body: err.toString(), - }; - } - - const { id, email, isverified } = decodedToken; - - const updateUser = async (parameters) => { - const query = `UPDATE ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users SET isverified = True WHERE id = ? IF EXISTS;`; - try { - const result = await client.execute(query, parameters, { prepare: true }); - // result would be undefined but query would be executed and entery is wirtten in the DB - return result; - } catch (ex) { - console.log('Error in createUser', ex.toString()); - } - }; - - if (isverified) { - return { - statusCode: 200, - body: JSON.stringify({ token }), - }; - } - - const { - rows: [applied], - } = await updateUser([id]); - - if (!applied['[applied]']) { - console.log('Unsuccessfull in updating user'); - return { - statusCode: 404, - }; - } - - const findUser = async (parameters) => { - const query = `SELECT * FROM ${ - process.env.NODE_ENV === 'development' - ? process.env.ASTRA_DB_KEYSPACE - : process.env.ASTRA_DB_KEYSPACE_PROD - }.users WHERE email = ? ALLOW FILTERING;`; - try { - const result = await client.execute(query, parameters, { prepare: true }); - - return result; - } catch (ex) { - console.log('Error in finduser', ex.toString()); - } - }; - const user = await findUser([email]); - const { - id: decodedId, - isverified: isVerified, - email: emailFromUser, - verificationstring, - } = user.rows[0]; - - const newToken = jwt.sign( - { - id: decodedId, - isverified: true, - email: emailFromUser, - firstname: '', - verificationstring, - profilepicurl: '', - }, - process.env.JWT_SECRET, - { expiresIn: '2h' }, - ); - - if (!newToken) - return { - statusCode: 403, - }; - - return { - statusCode: 200, - body: JSON.stringify({ token: newToken }), - }; -}; diff --git a/index.js b/index.js index 446c62f..6a361cf 100644 --- a/index.js +++ b/index.js @@ -5953,6 +5953,12 @@ var $author$project$Credentials$decodeToSession = F2( return $author$project$Credentials$Guest; } }); +var $author$project$Main$ChatPage = function (a) { + return {$: 'ChatPage', a: a}; +}; +var $author$project$Main$GotChatMsg = function (a) { + return {$: 'GotChatMsg', a: a}; +}; var $author$project$Main$GotHomeMsg = function (a) { return {$: 'GotHomeMsg', a: a}; }; @@ -5984,28 +5990,6 @@ var $author$project$Main$SignupPage = function (a) { var $author$project$Main$VerificationPage = function (a) { return {$: 'VerificationPage', a: a}; }; -var $author$project$Home$initialModel = {}; -var $elm$core$Platform$Cmd$batch = _Platform_batch; -var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); -var $author$project$Home$init = function (_v0) { - return _Utils_Tuple2($author$project$Home$initialModel, $elm$core$Platform$Cmd$none); -}; -var $author$project$Login$initialModel = { - errors: _List_Nil, - isLoading: false, - loginCredentials: {email: '', password: ''} -}; -var $author$project$Login$init = function (_v0) { - return _Utils_Tuple2($author$project$Login$initialModel, $elm$core$Platform$Cmd$none); -}; -var $author$project$Profile$GotTime = function (a) { - return {$: 'GotTime', a: a}; -}; -var $author$project$Profile$Intruder = {$: 'Intruder'}; -var $author$project$Profile$NotVerified = {$: 'NotVerified'}; -var $author$project$Profile$Verified = function (a) { - return {$: 'Verified', a: a}; -}; var $simonh1000$elm_jwt$Jwt$TokenDecodeError = function (a) { return {$: 'TokenDecodeError', a: a}; }; @@ -6668,6 +6652,77 @@ var $author$project$Credentials$fromTokenToString = function (_v0) { var string = _v0.a; return string; }; +var $author$project$Chat$initialModel = { + message: '', + receivedSocketData: { + clientId: '', + connectionId: '', + data: {message: ''}, + id: '', + name: '', + timestamp: 0 + } +}; +var $elm$json$Json$Encode$string = _Json_wrap; +var $author$project$Credentials$initiateSocketChannel = _Platform_outgoingPort('initiateSocketChannel', $elm$json$Json$Encode$string); +var $elm$core$Platform$Cmd$batch = _Platform_batch; +var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); +var $author$project$Credentials$userIdToString = function (_v0) { + var id = _v0.a; + return id; +}; +var $author$project$Chat$init = F2( + function (session, maybeSocket) { + var _v0 = $author$project$Credentials$fromSessionToToken(session); + if (_v0.$ === 'Just') { + var token = _v0.a; + var _v1 = A2( + $simonh1000$elm_jwt$Jwt$decodeToken, + $author$project$Credentials$decodeTokenData, + $author$project$Credentials$fromTokenToString(token)); + if (_v1.$ === 'Ok') { + var resultTokenRecord = _v1.a; + if (maybeSocket.$ === 'Just') { + var receivedSocketData = maybeSocket.a; + return _Utils_Tuple2( + _Utils_update( + $author$project$Chat$initialModel, + {receivedSocketData: receivedSocketData}), + $author$project$Credentials$initiateSocketChannel( + $author$project$Credentials$userIdToString(resultTokenRecord.id))); + } else { + return _Utils_Tuple2( + $author$project$Chat$initialModel, + $author$project$Credentials$initiateSocketChannel( + $author$project$Credentials$userIdToString(resultTokenRecord.id))); + } + } else { + return _Utils_Tuple2($author$project$Chat$initialModel, $elm$core$Platform$Cmd$none); + } + } else { + return _Utils_Tuple2($author$project$Chat$initialModel, $elm$core$Platform$Cmd$none); + } + }); +var $author$project$Home$initialModel = {}; +var $author$project$Home$init = function (_v0) { + return _Utils_Tuple2($author$project$Home$initialModel, $elm$core$Platform$Cmd$none); +}; +var $author$project$Login$initialModel = { + errors: _List_Nil, + isLoading: false, + loginCredentials: {email: '', password: ''} +}; +var $author$project$Login$init = function (_v0) { + return _Utils_Tuple2($author$project$Login$initialModel, $elm$core$Platform$Cmd$none); +}; +var $author$project$Profile$GotTime = function (a) { + return {$: 'GotTime', a: a}; +}; +var $author$project$Profile$Intruder = {$: 'Intruder'}; +var $author$project$Profile$NotVerified = {$: 'NotVerified'}; +var $author$project$Profile$Verified = function (a) { + return {$: 'Verified', a: a}; +}; var $author$project$Credentials$emptyImageString = $author$project$Credentials$ImageString($elm$core$Maybe$Nothing); var $author$project$Credentials$emptyUserId = $author$project$Credentials$UserId(''); var $author$project$Credentials$emptyVerificationString = $author$project$Credentials$VerificationString(''); @@ -6835,10 +6890,21 @@ var $author$project$Main$initCurrentPage = function (_v0) { page: $author$project$Main$HomePage(pageModel) }), A2($elm$core$Platform$Cmd$map, $author$project$Main$GotHomeMsg, pageCmds)); - case 'VerificationPage': - var _v5 = A2($author$project$Verification$init, model.session, url.path); + case 'ChatPage': + var _v5 = A2($author$project$Chat$init, model.session, $elm$core$Maybe$Nothing); var pageModel = _v5.a; var pageCmds = _v5.b; + return _Utils_Tuple2( + _Utils_update( + model, + { + page: $author$project$Main$ChatPage(pageModel) + }), + A2($elm$core$Platform$Cmd$map, $author$project$Main$GotChatMsg, pageCmds)); + case 'VerificationPage': + var _v6 = A2($author$project$Verification$init, model.session, url.path); + var pageModel = _v6.a; + var pageCmds = _v6.b; return _Utils_Tuple2( _Utils_update( model, @@ -6847,9 +6913,9 @@ var $author$project$Main$initCurrentPage = function (_v0) { }), A2($elm$core$Platform$Cmd$map, $author$project$Main$GotVerificationMsg, pageCmds)); default: - var _v6 = $author$project$Profile$init(model.session); - var pageModel = _v6.a; - var pageCmds = _v6.b; + var _v7 = $author$project$Profile$init(model.session); + var pageModel = _v7.a; + var pageCmds = _v7.b; return _Utils_Tuple2( _Utils_update( model, @@ -6859,6 +6925,7 @@ var $author$project$Main$initCurrentPage = function (_v0) { A2($elm$core$Platform$Cmd$map, $author$project$Main$GotProfileMsg, pageCmds)); } }; +var $author$project$Main$Chat = {$: 'Chat'}; var $author$project$Main$Home = {$: 'Home'}; var $author$project$Main$Login = {$: 'Login'}; var $author$project$Main$Profile = function (a) { @@ -7050,6 +7117,10 @@ var $author$project$Main$matchRoute = $elm$url$Url$Parser$oneOf( $elm$url$Url$Parser$s('signup')), A2( $elm$url$Url$Parser$map, + $author$project$Main$Chat, + $elm$url$Url$Parser$s('chat')), + A2( + $elm$url$Url$Parser$map, $author$project$Main$Verification, A2( $elm$url$Url$Parser$slash, @@ -7725,12 +7796,21 @@ var $author$project$Main$urlToPage = F2( } else { return $author$project$Main$NotFoundPage; } - case 'Home': + case 'Chat': var _v7 = _v0.a; + var _v8 = $author$project$Credentials$fromSessionToToken(session); + if (_v8.$ === 'Just') { + return $author$project$Main$ChatPage( + A2($author$project$Chat$init, session, $elm$core$Maybe$Nothing).a); + } else { + return $author$project$Main$NotFoundPage; + } + case 'Home': + var _v9 = _v0.a; return $author$project$Main$HomePage( $author$project$Home$init(_Utils_Tuple0).a); default: - var _v8 = _v0.a; + var _v10 = _v0.a; return $author$project$Main$NotFoundPage; } } else { @@ -7752,6 +7832,76 @@ var $author$project$Main$init = F3( var $author$project$Main$GotSubscriptionChangeMsg = function (a) { return {$: 'GotSubscriptionChangeMsg', a: a}; }; +var $author$project$Main$GotSubscriptionSocketMsg = function (a) { + return {$: 'GotSubscriptionSocketMsg', a: a}; +}; +var $elm$core$Platform$Sub$batch = _Platform_batch; +var $author$project$Credentials$SocketMessageData = F6( + function (name, id, clientId, connectionId, timestamp, data) { + return {clientId: clientId, connectionId: connectionId, data: data, id: id, name: name, timestamp: timestamp}; + }); +var $author$project$Credentials$DataMessage = function (message) { + return {message: message}; +}; +var $author$project$Credentials$decodeMessage = A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'message', + $elm$json$Json$Decode$string, + $elm$json$Json$Decode$succeed($author$project$Credentials$DataMessage)); +var $elm$json$Json$Decode$int = _Json_decodeInt; +var $author$project$Credentials$decodeSocketMessage = A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'data', + $author$project$Credentials$decodeMessage, + A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'timestamp', + $elm$json$Json$Decode$int, + A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'connectionId', + $elm$json$Json$Decode$string, + A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'clientId', + $elm$json$Json$Decode$string, + A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'id', + $elm$json$Json$Decode$string, + A3( + $NoRedInk$elm_json_decode_pipeline$Json$Decode$Pipeline$required, + 'name', + $elm$json$Json$Decode$string, + $elm$json$Json$Decode$succeed($author$project$Credentials$SocketMessageData))))))); +var $elm$core$Debug$toString = _Debug_toString; +var $author$project$Credentials$decodeToSocket = F2( + function (key, value) { + var result = A2($elm$json$Json$Decode$decodeValue, $author$project$Credentials$decodeSocketMessage, value); + if (result.$ === 'Ok') { + var obj = result.a; + return obj; + } else { + var err = result.a; + return { + clientId: '', + connectionId: '', + data: {message: ''}, + id: '', + name: $elm$core$Debug$toString(err), + timestamp: 0 + }; + } + }); +var $author$project$Credentials$publishSocketMessage = _Platform_incomingPort('publishSocketMessage', $elm$json$Json$Decode$value); +var $author$project$Credentials$socketMessageChanges = F2( + function (toMsg, key) { + return $author$project$Credentials$publishSocketMessage( + function (val) { + return toMsg( + A2($author$project$Credentials$decodeToSocket, key, val)); + }); + }); var $author$project$Credentials$onSessionChange = _Platform_incomingPort('onSessionChange', $elm$json$Json$Decode$value); var $author$project$Credentials$subscriptionChanges = F2( function (toMsg, key) { @@ -7762,7 +7912,12 @@ var $author$project$Credentials$subscriptionChanges = F2( }); }); var $author$project$Main$subscriptions = function (model) { - return A2($author$project$Credentials$subscriptionChanges, $author$project$Main$GotSubscriptionChangeMsg, model.key); + return $elm$core$Platform$Sub$batch( + _List_fromArray( + [ + A2($author$project$Credentials$subscriptionChanges, $author$project$Main$GotSubscriptionChangeMsg, model.key), + A2($author$project$Credentials$socketMessageChanges, $author$project$Main$GotSubscriptionSocketMsg, model.key) + ])); }; var $elm$browser$Browser$Navigation$load = _Browser_load; var $elm$core$Maybe$destruct = F3( @@ -7775,7 +7930,6 @@ var $elm$core$Maybe$destruct = F3( } }); var $elm$json$Json$Encode$null = _Json_encodeNull; -var $elm$json$Json$Encode$string = _Json_wrap; var $author$project$Credentials$storeSession = _Platform_outgoingPort( 'storeSession', function ($) { @@ -7827,6 +7981,24 @@ var $elm$url$Url$toString = function (url) { _Utils_ap(http, url.host)), url.path))); }; +var $author$project$Credentials$sendMessageToSocket = _Platform_outgoingPort('sendMessageToSocket', $elm$json$Json$Encode$string); +var $author$project$Chat$update = F2( + function (msg, model) { + if (msg.$ === 'StoreMessage') { + var message = msg.a; + return _Utils_Tuple2( + _Utils_update( + model, + {message: message}), + $elm$core$Platform$Cmd$none); + } else { + return _Utils_Tuple2( + _Utils_update( + model, + {message: ''}), + $author$project$Credentials$sendMessageToSocket(model.message)); + } + }); var $author$project$Home$update = F2( function (msg, model) { return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); @@ -8162,7 +8334,7 @@ var $author$project$Login$submitLogin = function (credentials) { body: $elm$http$Http$jsonBody( $author$project$Login$credentialsEncoder(credentials)), expect: A2($elm$http$Http$expectJson, $author$project$Login$LoginDone, $author$project$Credentials$tokenDecoder), - url: '/.netlify/functions/login-api' + url: '/api/login' }); }; var $author$project$Login$BadEmail = function (a) { @@ -8321,7 +8493,6 @@ var $elm$file$File$Select$file = F2( _File_uploadOne(mimes)); }); var $elm$json$Json$Decode$float = _Json_decodeFloat; -var $elm$json$Json$Decode$int = _Json_decodeInt; var $elm$core$Basics$round = _Basics_round; var $simonh1000$elm_jwt$Jwt$getTokenExpirationMillis = function (token) { var decodeExp = $elm$json$Json$Decode$oneOf( @@ -8414,7 +8585,7 @@ var $author$project$Profile$submitProfile = F2( method: 'PUT', timeout: $elm$core$Maybe$Nothing, tracker: $elm$core$Maybe$Nothing, - url: '/.netlify/functions/profile-put-api' + url: '/api/profile' }); } else { return $elm$core$Platform$Cmd$none; @@ -8623,7 +8794,7 @@ var $author$project$Signup$submitSignup = function (credentials) { body: $elm$http$Http$jsonBody( $author$project$Signup$credentialsEncoder(credentials)), expect: A2($elm$http$Http$expectJson, $author$project$Signup$SignupDone, $author$project$Credentials$tokenDecoder), - url: '/.netlify/functions/signup-api' + url: '/api/signup' }); }; var $author$project$Signup$BadEmail = function (a) { @@ -8765,7 +8936,7 @@ var $author$project$Verification$apiCallToVerify = function (session) { method: 'PUT', timeout: $elm$core$Maybe$Nothing, tracker: $elm$core$Maybe$Nothing, - url: '/.netlify/functions/verify-put-api' + url: '/api/verify' }); } else { return $elm$core$Platform$Cmd$none; @@ -8809,10 +8980,6 @@ var $author$project$Verification$update = F2( A2($elm$json$Json$Encode$encode, 0, tokenValue)))); } }); -var $author$project$Credentials$userIdToString = function (_v0) { - var id = _v0.a; - return id; -}; var $author$project$Main$update = F2( function (msg, model) { switch (msg.$) { @@ -8896,14 +9063,32 @@ var $author$project$Main$update = F2( } else { return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); } + case 'GotChatMsg': + var chatMsg = msg.a; + var _v8 = model.page; + if (_v8.$ === 'ChatPage') { + var chatModel = _v8.a; + var _v9 = A2($author$project$Chat$update, chatMsg, chatModel); + var chatModelFromChat = _v9.a; + var chatMsgFromChat = _v9.b; + return _Utils_Tuple2( + _Utils_update( + model, + { + page: $author$project$Main$ChatPage(chatModelFromChat) + }), + A2($elm$core$Platform$Cmd$map, $author$project$Main$GotChatMsg, chatMsgFromChat)); + } else { + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + } case 'GotVerificationMsg': var verificationMsg = msg.a; - var _v8 = model.page; - if (_v8.$ === 'VerificationPage') { - var verificationModel = _v8.a; - var _v9 = A2($author$project$Verification$update, verificationMsg, verificationModel); - var verificationModelFromVerification = _v9.a; - var verificationMsgFromVerification = _v9.b; + var _v10 = model.page; + if (_v10.$ === 'VerificationPage') { + var verificationModel = _v10.a; + var _v11 = A2($author$project$Verification$update, verificationMsg, verificationModel); + var verificationModelFromVerification = _v11.a; + var verificationMsgFromVerification = _v11.b; return _Utils_Tuple2( _Utils_update( model, @@ -8916,12 +9101,12 @@ var $author$project$Main$update = F2( } case 'GotSignupMsg': var signupMsg = msg.a; - var _v10 = model.page; - if (_v10.$ === 'SignupPage') { - var signupModel = _v10.a; - var _v11 = A2($author$project$Signup$update, signupMsg, signupModel); - var signupModelFromSignup = _v11.a; - var signupMsgFromSignup = _v11.b; + var _v12 = model.page; + if (_v12.$ === 'SignupPage') { + var signupModel = _v12.a; + var _v13 = A2($author$project$Signup$update, signupMsg, signupModel); + var signupModelFromSignup = _v13.a; + var signupMsgFromSignup = _v13.b; return _Utils_Tuple2( _Utils_update( model, @@ -8939,15 +9124,15 @@ var $author$project$Main$update = F2( model, {session: session}), function () { - var _v12 = $author$project$Credentials$fromSessionToToken(session); - if (_v12.$ === 'Just') { - var token = _v12.a; - var _v13 = A2( + var _v14 = $author$project$Credentials$fromSessionToToken(session); + if (_v14.$ === 'Just') { + var token = _v14.a; + var _v15 = A2( $simonh1000$elm_jwt$Jwt$decodeToken, $author$project$Credentials$decodeTokenData, $author$project$Credentials$fromTokenToString(token)); - if (_v13.$ === 'Ok') { - var resultTokenRecord = _v13.a; + if (_v15.$ === 'Ok') { + var resultTokenRecord = _v15.a; return A2( $elm$browser$Browser$Navigation$pushUrl, model.key, @@ -8959,8 +9144,25 @@ var $author$project$Main$update = F2( return A2($elm$browser$Browser$Navigation$pushUrl, model.key, '/login'); } }()); + case 'GotSubscriptionSocketMsg': + var socketMsgObj = msg.a; + var _v16 = A2( + $author$project$Chat$init, + model.session, + $elm$core$Maybe$Just(socketMsgObj)); + var chatModelFromChat = _v16.a; + var chatMsgFromChat = _v16.b; + return _Utils_Tuple2( + _Utils_update( + model, + { + page: $author$project$Main$ChatPage(chatModelFromChat) + }), + A2($elm$core$Platform$Cmd$map, $author$project$Main$GotChatMsg, chatMsgFromChat)); case 'GetLogout': return _Utils_Tuple2(model, $author$project$Credentials$logout); + case 'ChatNewMessage': + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); default: return _Utils_Tuple2( _Utils_update( @@ -8974,42 +9176,15 @@ var $elm$html$Html$map = $elm$virtual_dom$VirtualDom$map; var $elm$html$Html$p = _VirtualDom_node('p'); var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; -var $elm$html$Html$div = _VirtualDom_node('div'); -var $elm$html$Html$h2 = _VirtualDom_node('h2'); -var $author$project$Home$view = function (model) { - return A2( - $elm$html$Html$div, - _List_Nil, - _List_fromArray( - [ - A2( - $elm$html$Html$h2, - _List_Nil, - _List_fromArray( - [ - $elm$html$Html$text('Hello and welcome to our awesome website !') - ])), - A2( - $elm$html$Html$p, - _List_Nil, - _List_fromArray( - [ - $elm$html$Html$text('which is still under construction') - ])) - ])); -}; -var $author$project$Login$LoginSubmit = function (a) { - return {$: 'LoginSubmit', a: a}; -}; -var $author$project$Login$StoreEmail = function (a) { - return {$: 'StoreEmail', a: a}; -}; -var $author$project$Login$StorePassword = function (a) { - return {$: 'StorePassword', a: a}; +var $author$project$Chat$MessageSubmit = {$: 'MessageSubmit'}; +var $author$project$Chat$StoreMessage = function (a) { + return {$: 'StoreMessage', a: a}; }; var $elm$html$Html$br = _VirtualDom_node('br'); var $elm$html$Html$button = _VirtualDom_node('button'); +var $elm$html$Html$div = _VirtualDom_node('div'); var $elm$html$Html$form = _VirtualDom_node('form'); +var $elm$html$Html$h2 = _VirtualDom_node('h2'); var $elm$html$Html$input = _VirtualDom_node('input'); var $elm$virtual_dom$VirtualDom$Normal = function (a) { return {$: 'Normal', a: a}; @@ -9065,6 +9240,99 @@ var $elm$html$Html$Attributes$stringProperty = F2( var $elm$html$Html$Attributes$type_ = $elm$html$Html$Attributes$stringProperty('type'); var $elm$html$Html$ul = _VirtualDom_node('ul'); var $elm$html$Html$Attributes$value = $elm$html$Html$Attributes$stringProperty('value'); +var $author$project$Chat$view = function (model) { + return A2( + $elm$html$Html$div, + _List_Nil, + _List_fromArray( + [ + A2( + $elm$html$Html$h2, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text('Chat') + ])), + A2( + $elm$html$Html$div, + _List_Nil, + _List_fromArray( + [ + A2( + $elm$html$Html$ul, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text(model.receivedSocketData.data.message) + ])) + ])), + A2( + $elm$html$Html$form, + _List_Nil, + _List_fromArray( + [ + A2( + $elm$html$Html$div, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text('SomeUser:'), + A2($elm$html$Html$br, _List_Nil, _List_Nil), + A2( + $elm$html$Html$input, + _List_fromArray( + [ + $elm$html$Html$Attributes$type_('text'), + $elm$html$Html$Events$onInput($author$project$Chat$StoreMessage), + $elm$html$Html$Attributes$value(model.message) + ]), + _List_Nil) + ])), + A2( + $elm$html$Html$button, + _List_fromArray( + [ + $elm$html$Html$Attributes$type_('button'), + $elm$html$Html$Events$onClick($author$project$Chat$MessageSubmit) + ]), + _List_fromArray( + [ + $elm$html$Html$text('Send message') + ])) + ])) + ])); +}; +var $author$project$Home$view = function (model) { + return A2( + $elm$html$Html$div, + _List_Nil, + _List_fromArray( + [ + A2( + $elm$html$Html$h2, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text('Hello and welcome to our awesome website !') + ])), + A2( + $elm$html$Html$p, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text('which is still under construction') + ])) + ])); +}; +var $author$project$Login$LoginSubmit = function (a) { + return {$: 'LoginSubmit', a: a}; +}; +var $author$project$Login$StoreEmail = function (a) { + return {$: 'StoreEmail', a: a}; +}; +var $author$project$Login$StorePassword = function (a) { + return {$: 'StorePassword', a: a}; +}; var $elm$html$Html$li = _VirtualDom_node('li'); var $author$project$Login$viewError = function (checkErrors) { switch (checkErrors.$) { @@ -9799,6 +10067,12 @@ var $author$project$Main$content = function (model) { $elm$html$Html$map, $author$project$Main$GotHomeMsg, $author$project$Home$view(homeModel)); + case 'ChatPage': + var chatModel = _v0.a; + return A2( + $elm$html$Html$map, + $author$project$Main$GotChatMsg, + $author$project$Chat$view(chatModel)); case 'VerificationPage': var verificationModel = _v0.a; return A2( @@ -9955,6 +10229,14 @@ var $author$project$Main$isActive = function (_v0) { var _v7 = _v1.a; return false; } + case 'Chat': + if (_v1.b.$ === 'ChatPage') { + var _v8 = _v1.a; + return true; + } else { + var _v9 = _v1.a; + return false; + } case 'Verification': if (_v1.b.$ === 'VerificationPage') { return true; @@ -9962,13 +10244,12 @@ var $author$project$Main$isActive = function (_v0) { return false; } default: - var _v8 = _v1.a; + var _v10 = _v1.a; return false; } }; var $elm$html$Html$nav = _VirtualDom_node('nav'); var $elm$html$Html$span = _VirtualDom_node('span'); -var $elm$core$Debug$toString = _Debug_toString; var $elm$html$Html$Attributes$width = function (n) { return A2( _VirtualDom_attribute, @@ -10185,23 +10466,49 @@ var $author$project$Main$viewHeader = function (_v0) { $elm$core$Debug$toString(err)) ])); } - }() - ])), - A2( - $elm$html$Html$li, - _List_Nil, - _List_fromArray( - [ + }(), A2( - $elm$html$Html$a, + $elm$html$Html$li, _List_fromArray( [ - $elm$html$Html$Attributes$href('/'), - $elm$html$Html$Events$onClick($author$project$Main$GetLogout) + $elm$html$Html$Attributes$classList( + _List_fromArray( + [ + _Utils_Tuple2( + 'active', + $author$project$Main$isActive( + {link: $author$project$Main$Chat, page: page})) + ])) ]), _List_fromArray( [ - $elm$html$Html$text('logout') + A2( + $elm$html$Html$a, + _List_fromArray( + [ + $elm$html$Html$Attributes$href('/chat') + ]), + _List_fromArray( + [ + $elm$html$Html$text('Chat') + ])) + ])), + A2( + $elm$html$Html$li, + _List_Nil, + _List_fromArray( + [ + A2( + $elm$html$Html$a, + _List_fromArray( + [ + $elm$html$Html$Attributes$href('/'), + $elm$html$Html$Events$onClick($author$project$Main$GetLogout) + ]), + _List_fromArray( + [ + $elm$html$Html$text('logout') + ])) ])) ])) ])); diff --git a/netlify.toml b/netlify.toml deleted file mode 100755 index fcf3322..0000000 --- a/netlify.toml +++ /dev/null @@ -1,16 +0,0 @@ -[functions] - directory = "functions/" - -[[redirects]] - from = "/*" - to = "index.html" - status = 200 - -[dev] - targetPort = 8000 - -[context.production] - NODE_ENV = "production" - -[context.dev.environment] - NODE_ENV = "development" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index adfb86f..14c27ae 100755 --- a/package-lock.json +++ b/package-lock.json @@ -11,15 +11,22 @@ "dependencies": { "@sendgrid/mail": "^7.7.0", "bcrypt": "^5.0.1", + "body-parser": "^1.20.1", "cassandra-driver": "^4.6.4", + "dotenv": "^16.0.3", "elm": "^0.19.1-5", "elm-hot": "^1.0.1", "elm-live": "^4.0.2", + "express": "^4.18.2", "firebase": "^9.15.0", "jsonwebtoken": "^8.5.1", "netlify-cli": "^12.0.11", "prettier": "^2.2.1", + "socket.io": "^4.5.4", "uuid": "^8.3.2" + }, + "devDependencies": { + "nodemon": "^2.0.20" } }, "node_modules/@firebase/analytics": { @@ -723,6 +730,24 @@ "node": "6.* || 8.* || >=10.*" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -738,6 +763,18 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/adm-zip": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", @@ -780,6 +817,18 @@ "node": ">=8" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -810,6 +859,11 @@ "node": ">= 6" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -862,6 +916,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", @@ -888,6 +950,64 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -897,11 +1017,42 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/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": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -929,6 +1080,33 @@ "node": "*" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -976,6 +1154,69 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/crocks": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/crocks/-/crocks-0.12.1.tgz", @@ -1064,6 +1305,23 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -1072,6 +1330,14 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1157,37 +1423,6 @@ "node": ">=0.10.0" } }, - "node_modules/elm-live/node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/elm-live/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/elm-live/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/elm-live/node_modules/chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -1269,45 +1504,10 @@ "resolved": "https://registry.npmjs.org/elm-hot/-/elm-hot-1.1.4.tgz", "integrity": "sha512-qPDP/o/Fkifriaxaf3E7hHFB5L6Ijihyg8is4A6xna6/h/zebUiNssbQrxywI2oxNUkr6W/leEu/WlIC1tmVnw==" }, - "node_modules/elm-live/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/elm-live/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/elm-live/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/elm-live/node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "node_modules/elm-live/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -1319,25 +1519,6 @@ "node": ">= 0.6" } }, - "node_modules/elm-live/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/elm-live/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/elm-live/node_modules/lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -1374,17 +1555,6 @@ "node": ">= 0.8" } }, - "node_modules/elm-live/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/elm-live/node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -1465,17 +1635,6 @@ "node": ">=0.8.0" } }, - "node_modules/elm-live/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/elm-live/node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -1518,6 +1677,54 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/es6-promisify": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", @@ -1574,6 +1781,118 @@ "node": ">=6" } }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1608,6 +1927,17 @@ "node": ">=0.8.0" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -1730,6 +2060,14 @@ "node": ">= 0.12" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -1754,6 +2092,24 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -1781,6 +2137,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -1819,6 +2188,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1840,6 +2220,17 @@ "node": ">=6" } }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -1859,11 +2250,46 @@ "node": ">=0.10.0" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", @@ -1908,11 +2334,28 @@ "node": ">= 6" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/idb": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1955,6 +2398,17 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -1987,6 +2441,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -2183,6 +2645,38 @@ "is-buffer": "~1.1.6" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2241,6 +2735,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netlify-cli": { "version": "12.0.11", "resolved": "https://registry.npmjs.org/netlify-cli/-/netlify-cli-12.0.11.tgz", @@ -25833,6 +26335,43 @@ } } }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -25893,6 +26432,25 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -25952,6 +26510,11 @@ "node": ">=4" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/pem": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.2.tgz", @@ -26024,6 +26587,18 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -26034,6 +26609,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -26067,6 +26648,31 @@ "node": ">= 0.6" } }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -26138,11 +26744,71 @@ "semver": "bin/semver" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -26162,11 +26828,78 @@ "node": ">=0.10.0" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -26191,6 +26924,14 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -26231,6 +26972,18 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -26258,6 +27011,52 @@ "node": ">=10" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/touch/node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -26296,6 +27095,24 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -26317,6 +27134,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -26325,6 +27150,14 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index c5053df..1556e0e 100755 --- a/package.json +++ b/package.json @@ -3,21 +3,28 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "elm-live src/Main.elm --pushstate -- --output=index.js" + "start": "NODE_ENV=development nodemon app.js & elm-live src/Main.elm --proxy-prefix=/api --proxy-host=http://localhost:8080/api --pushstate -- --output=index.js" }, "author": "Dusan Stevanovic", "license": "ISC", "dependencies": { "@sendgrid/mail": "^7.7.0", "bcrypt": "^5.0.1", + "body-parser": "^1.20.1", "cassandra-driver": "^4.6.4", + "dotenv": "^16.0.3", "elm": "^0.19.1-5", "elm-hot": "^1.0.1", "elm-live": "^4.0.2", + "express": "^4.18.2", "firebase": "^9.15.0", "jsonwebtoken": "^8.5.1", "netlify-cli": "^12.0.11", "prettier": "^2.2.1", + "socket.io": "^4.5.4", "uuid": "^8.3.2" + }, + "devDependencies": { + "nodemon": "^2.0.20" } } diff --git a/readme.md b/readme.md index 46e2278..9384497 100755 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ # My Elm app -## Full authentication flow via serverless functions and Elm +## Full authentication/authorization flow via Node and Elm -### Elm / Astra DataStax / Firebase Storage / Netlify +### Elm / Astra DataStax / Firebase Storage / Express - Node == v14 -- Run: netlify dev +- Run: npm run start diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..e70c974 --- /dev/null +++ b/server/index.js @@ -0,0 +1,7 @@ +const loginRoute = require('./login-api'); +const signupRoute = require('./signup-api'); +const profileRoute = require('./profile-put-api'); +const verifyRoute = require('./verify-put-api'); +const socketRoute = require('./socket-api'); + +exports.routes = [loginRoute, signupRoute, profileRoute, verifyRoute]; diff --git a/server/login-api.js b/server/login-api.js new file mode 100644 index 0000000..20a6a5d --- /dev/null +++ b/server/login-api.js @@ -0,0 +1,79 @@ +// const { createClient } = require('@astrajs/collections'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const { v4: uuid } = require('uuid'); +const { clientPromise } = require('../connect-database'); + +exports.module = { + path: '/api/login', + method: 'post', + handler: async (req, res) => { + const { body } = req; + const { email, password } = body; + + let client; + + try { + client = await clientPromise; + } catch (err) { + console.log('Client: ', err); + client = err.toString(); + } + + if (!client || typeof client?.execute !== 'function') { + return res.status(500).json(`{message: There is no client, payload: ${client}}`); + } + + const findUser = async (parameters) => { + const query = `SELECT * FROM ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users WHERE email = ? ALLOW FILTERING;`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + + return result; + } catch (ex) { + console.log('Error in finduser', ex.toString()); + } + }; + const user = await findUser([email]); + + if (!user?.rows?.length && !user?.rows[0]) { + return res.sendStatus(401); + } + + const { id, isverified, passwordhash, salt, firstname, verificationstring, avatarurl } = + user.rows[0]; + const pepper = process.env.PEPPER_STRING; + + const isCorrect = await bcrypt.compare(salt + password + pepper, passwordhash); + + if (isCorrect) { + jwt.sign( + { + id, + isverified, + email, + firstname, + verificationstring, + profilepicurl: avatarurl || '', + }, + process.env.JWT_SECRET, + { + expiresIn: '2h', + }, + (err, token) => { + if (err) { + res.sendStatus(500); + } + + res.status(200).json({ token }); + }, + ); + } else { + return res.sendStatus(401); + } + }, +}; diff --git a/server/profile-put-api.js b/server/profile-put-api.js new file mode 100644 index 0000000..9fea6dd --- /dev/null +++ b/server/profile-put-api.js @@ -0,0 +1,148 @@ +// const { createClient } = require('@astrajs/collections'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const { v4: uuid } = require('uuid'); +const { clientPromise } = require('../connect-database'); +const querystring = require('querystring').escape; +const initializeApp = require('firebase/app').initializeApp; +const { + getStorage, + ref, + uploadBytes, + getDownloadURL, + getStream, + uploadString, +} = require('firebase/storage'); + +exports.module = { + path: '/api/profile', + method: 'put', + handler: async (req, res) => { + const { body } = req; + const { email, firstname, imagefile } = body; + const { authorization } = req.headers; + const client = await clientPromise; + + if (!client) + return res.status(500).json(`{message: There is no client, payload: ${client}}`); + + if (!authorization) { + return res.status(401).json('No authorization header sent'); + } + const token = authorization.split(' ')[1]; + // const user = await usersCollection.findOne({ email: { $eq: email } }); + + let decodedToken; + + try { + decodedToken = await jwt.verify(token, process.env.JWT_SECRET); + } catch (err) { + return res.sendStatus(401); + } + + const firebaseConfig = { + apiKey: process.env.FIREBASE_API_KEY, + authDomain: process.env.FIREBASE_AUTH_DOMAIN, + projectId: process.env.FIREBASE_PROJECT_ID, + storageBucket: process.env.FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.FIREBASE_APP_ID, + measurementId: process.env.FIREBASE_MEASUREMENT_ID, + }; + + const app = initializeApp(firebaseConfig); + const storage = getStorage(app); + + const { id } = decodedToken; + + let photoUrl = ''; + + try { + if (imagefile.length > 0) { + await uploadString( + ref(storage, `/images/profile-pic-${id}.jpeg`), + imagefile, + 'data_url', + ); + const downloadUrl = await getDownloadURL( + ref(storage, `/images/profile-pic-${id}.jpeg`), + ); + photoUrl = + downloadUrl.length > 0 + ? `https://firebasestorage.googleapis.com/v0/b/${ + process.env.FIREBASE_STORAGE_BUCKET + }/o/${querystring(`images/profile-pic-${id}.jpeg`)}?alt=media` + : ''; + } + } catch (err) { + return res.sendStatus(403); + } + + const updateUser = async (parameters) => { + const query = `UPDATE ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users SET firstname = ?, avatarurl = ? WHERE id = ? IF EXISTS;`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + // result would be undefined but query would be executed and entery is written in the DB + return result; + } catch (ex) { + console.log('Error in createUser', ex.toString()); + } + }; + + const { + rows: [applied], + } = await updateUser([firstname, photoUrl, id]); + + if (!applied['[applied]']) { + console.log('Unsuccessfull in updating user'); + return res.sendStatus(404); + } + + const findUser = async (parameters) => { + const query = `SELECT * FROM ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users WHERE email = ? ALLOW FILTERING;`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + + return result; + } catch (ex) { + console.log('Error in finduser', ex.toString()); + } + }; + const user = await findUser([email]); + const { + id: decodedId, + isverified: isVerified, + email: emailFromUser, + firstname: firstName, + verificationstring, + avatarurl, + } = user.rows[0]; + + return jwt.sign( + { + id: decodedId, + isverified: isVerified, + email: emailFromUser, + firstname: firstName, + verificationstring, + profilepicurl: avatarurl || '', + }, + process.env.JWT_SECRET, + { expiresIn: '2h' }, + (err, token) => { + if (err) { + return res.status(200).json(err); + } + res.status(200).json({ token }); + }, + ); + }, +}; diff --git a/server/signup-api.js b/server/signup-api.js new file mode 100644 index 0000000..383fe26 --- /dev/null +++ b/server/signup-api.js @@ -0,0 +1,138 @@ +// const { createClient } = require('@astrajs/collections'); +const cassandra = require('cassandra-driver'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const { v4: uuid } = require('uuid'); +const { clientPromise } = require('../connect-database'); +const sendgrid = require('@sendgrid/mail'); + +sendgrid.setApiKey(process.env.SENDGRID_API_KEY); + +const sendEmail = ({ to, from, subject, text, html = '' }) => { + const msg = { to, from, subject, text, html }; + return sendgrid.send(msg); +}; + +exports.module = { + path: '/api/signup', + method: 'post', + handler: async (req, res) => { + const { body } = req; + const { email, password } = body; + let client; + + try { + client = await clientPromise; + } catch (err) { + console.log('Client: ', err); + client = err.toString(); + } + + if (!client || typeof client?.execute !== 'function') { + return res.status(500).json(`{message: There is no client, payload: ${client}}`); + } + + const findUser = async (parameters) => { + const query = `SELECT * FROM ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users WHERE email = ? ALLOW FILTERING;`; + try { + const result = client.execute(query, parameters, { prepare: true }); + return result; + } catch (ex) { + console.log('Error in finduser', ex.toString()); + } + }; + + const user = await findUser([email]); + + if (user?.rows[0]) { + // conflict error code + return res.sendStatus(401); + } + const salt = uuid(); + const pepper = process.env.PEPPER_STRING; + const passwordHash = await bcrypt.hash(salt + password + pepper, 10); // 2 arg is num of iterations to + const verificationString = uuid(); + const createdId = uuid(); + + const createUser = async (parameters) => { + const query = `INSERT INTO ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users (id, email, firstname, isadmin, isverified, lastname, passwordhash, salt, verificationstring, avatarurl) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + // result would be undefined but query would be executed and entery is wirtten in the DB + return result; + } catch (ex) { + console.log('Error in createUser', ex.toString()); + } + }; + + const createdUser = await createUser([ + createdId, + email, + '', + false, + false, + '', + passwordHash, + salt, + verificationString, + '', + ]); + + try { + await sendEmail({ + to: email, + from: 'dooshanstevanovic@gmail.com', + subject: 'Please verify your email', + text: `Thanks for signin up ! To verify your email click here: ${ + process.env.NODE_ENV === 'development' + ? 'http://localhost:8000' + : 'https://my-elm-app.netlify.app' + }/verify-email/${verificationString}`, + html: `
+

Hello !

+
+

Thanks for signin up !

+

To verify your email click here

+
+
`, + }); + } catch (err) { + console.log('Error in sendEmail', err.toString()); + return { + statusCode: 500, + }; + } + + return jwt.sign( + { + id: createdId, + isverified: false, + email, + firstname: '', + verificationstring: verificationString, + profilepicurl: '', + }, + process.env.JWT_SECRET, + { expiresIn: '2h' }, + (err, token) => { + if (err) { + res.sendStatus(500); + } + + res.status(200).json({ token }); + }, + ); + }, +}; diff --git a/server/socket-api.js b/server/socket-api.js new file mode 100644 index 0000000..a8c0641 --- /dev/null +++ b/server/socket-api.js @@ -0,0 +1,9 @@ +exports.handler = async function (req) { + const clientId = req.queryStringParameters['clientId'] || 'NO_CLIENT_ID'; + + return { + statusCode: 200, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify('123'), + }; +}; diff --git a/functions/tournament-api.js b/server/tournament-api.js similarity index 100% rename from functions/tournament-api.js rename to server/tournament-api.js diff --git a/server/verify-put-api.js b/server/verify-put-api.js new file mode 100644 index 0000000..7bd02aa --- /dev/null +++ b/server/verify-put-api.js @@ -0,0 +1,101 @@ +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const { v4: uuid } = require('uuid'); +const { clientPromise } = require('../connect-database'); + +exports.module = { + path: '/api/verify', + method: 'put', + handler: async (req, res) => { + const { authorization } = req.headers; + const client = await clientPromise; + + if (!client) + return res.status(500).json(`{message: There is no client, payload: ${client}}`); + + if (!authorization) { + return res.status(401).json('No authorization header sent'); + } + + const token = authorization.split(' ')[1]; + + let decodedToken; + + try { + decodedToken = await jwt.verify(token, process.env.JWT_SECRET); + } catch (err) { + return res.sendStatus(401); + } + + const { id, email, isverified } = decodedToken; + + const updateUser = async (parameters) => { + const query = `UPDATE ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users SET isverified = True WHERE id = ? IF EXISTS;`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + // result would be undefined but query would be executed and entery is wirtten in the DB + return result; + } catch (ex) { + console.log('Error in createUser', ex.toString()); + } + }; + + if (isverified) { + return res.status(200).json(token); + } + + const { + rows: [applied], + } = await updateUser([id]); + + if (!applied['[applied]']) { + console.log('Unsuccessfull in updating user'); + return res.sendStatus(404); + } + + const findUser = async (parameters) => { + const query = `SELECT * FROM ${ + process.env.NODE_ENV === 'development' + ? process.env.ASTRA_DB_KEYSPACE + : process.env.ASTRA_DB_KEYSPACE_PROD + }.users WHERE email = ? ALLOW FILTERING;`; + try { + const result = await client.execute(query, parameters, { prepare: true }); + + return result; + } catch (ex) { + console.log('Error in finduser', ex.toString()); + } + }; + const user = await findUser([email]); + const { + id: decodedId, + isverified: isVerified, + email: emailFromUser, + verificationstring, + } = user.rows[0]; + + return jwt.sign( + { + id: decodedId, + isverified: true, + email: emailFromUser, + firstname: '', + verificationstring, + profilepicurl: '', + }, + process.env.JWT_SECRET, + { expiresIn: '2h' }, + (err, token) => { + if (err) { + return res.status(200).json(err); + } + res.status(200).json({ token }); + }, + ); + }, +}; diff --git a/src/Chat.elm b/src/Chat.elm new file mode 100644 index 0000000..9719ce7 --- /dev/null +++ b/src/Chat.elm @@ -0,0 +1,107 @@ +module Chat exposing (..) + +import Credentials exposing (Session, SocketMessageData, decodeTokenData, fromSessionToToken, fromTokenToString, initiateSocketChannel, sendMessageToSocket, userIdToString) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Jwt + + +type alias Model = + { message : String, receivedSocketData : SocketMessageData } + + +type CheckErrors + = BadInput String + | BadRequest String + + +type Msg + = StoreMessage String + | MessageSubmit + + +initialModel : Model +initialModel = + { message = "" + , receivedSocketData = + { name = "" + , id = "" + , clientId = "" + , connectionId = "" + , timestamp = 0 + , data = + { message = "" + } + } + } + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + StoreMessage message -> + ( { model | message = message }, Cmd.none ) + + MessageSubmit -> + ( { model | message = "" }, sendMessageToSocket model.message ) + + +init : Session -> Maybe SocketMessageData -> ( Model, Cmd Msg ) +init session maybeSocket = + case fromSessionToToken session of + Just token -> + case Jwt.decodeToken decodeTokenData <| fromTokenToString token of + Ok resultTokenRecord -> + case maybeSocket of + Just receivedSocketData -> + ( { initialModel | receivedSocketData = receivedSocketData }, initiateSocketChannel <| userIdToString resultTokenRecord.id ) + + Nothing -> + ( initialModel, initiateSocketChannel <| userIdToString resultTokenRecord.id ) + + Err _ -> + ( initialModel, Cmd.none ) + + Nothing -> + ( initialModel, Cmd.none ) + + + +-- Just receivedSocketData ) +-- { initialModel | receivedSocketData = receivedSocketData } +-- ( Nothing, Nothing ) -> +-- ( initialModel, Cmd.none ) +-- ( Just _, Nothing ) -> +-- ( initialModel, Cmd.none ) +-- ( Nothing, Just _ ) -> +-- ( initialModel, Cmd.none ) + + +view : Model -> Html Msg +view model = + div + [] + [ h2 [] + [ text "Chat" ] + , div [] + [ ul [] [ text model.receivedSocketData.data.message ] + ] + , Html.form [] + [ div [] + [ text "SomeUser:" + , br [] [] + , input + [ type_ "text" + , onInput StoreMessage + , value model.message + ] + [] + ] + , button + [ type_ "button" + , onClick MessageSubmit + ] + [ text "Send message" ] + ] + ] diff --git a/src/Credentials.elm b/src/Credentials.elm index a544e0f..2d9438e 100644 --- a/src/Credentials.elm +++ b/src/Credentials.elm @@ -1,6 +1,7 @@ port module Credentials exposing ( ImageString , Session + , SocketMessageData , Token , UnwrappedTokenData , UserId @@ -17,8 +18,11 @@ port module Credentials exposing , fromTokenToString , guest , imageStringToMaybeString + , initiateSocketChannel , logout , onSessionChange + , sendMessageToSocket + , socketMessageChanges , storeSession , stringToImageString , subscriptionChanges @@ -31,8 +35,8 @@ port module Credentials exposing import Browser.Navigation as Nav import Http exposing (Header, header) -import Json.Decode as Decode exposing (Decoder, Value, at, bool, map6, string) -import Json.Decode.Pipeline exposing (required) +import Json.Decode as Decode exposing (Decoder, Value, at, bool, int, map6, string) +import Json.Decode.Pipeline exposing (required, requiredAt) import Json.Encode as Encode exposing (Value) import Url.Parser exposing (Parser, custom) @@ -54,6 +58,43 @@ type UserId = UserId String +type alias SocketMessageData = + { name : String + , id : String + , clientId : String + , connectionId : String + , timestamp : Int + , data : DataMessage + } + + +type alias DataMessage = + { message : String + } + + + +-- t{ name = Debug.toString err +-- , id = "" +-- , clientId = "" +-- , connectionId = "" +-- , timestamp = 0 +-- , data = +-- { message = "" +-- } +-- } +-- { +-- name: 'hello-world-message', +-- id: 'W46cHmYS-f:5:0', +-- clientId: '123', +-- connectionId: 'W46cHmYS-f', +-- encoding: null, +-- data: { +-- message: '{msgType: hello-there-guys, msgText: Hello from Elm Side !}', +-- }, +-- }; + + userIdToString : UserId -> String userIdToString (UserId id) = id @@ -201,14 +242,23 @@ encodeToken (Token token) = port storeSession : Maybe String -> Cmd msg +port onSessionChange : (Value -> msg) -> Sub msg + + +port publishSocketMessage : (Value -> msg) -> Sub msg + + +port initiateSocketChannel : String -> Cmd msg + + +port sendMessageToSocket : String -> Cmd msg + + logout : Cmd msg logout = storeSession Nothing -port onSessionChange : (Value -> msg) -> Sub msg - - decodeTokenData : Decoder UnwrappedTokenData decodeTokenData = map6 UnwrappedTokenData @@ -242,11 +292,83 @@ decodeToSession key value = Guest +decodeSocketMessage : Decoder SocketMessageData +decodeSocketMessage = + Decode.succeed SocketMessageData + |> required "name" string + |> required "id" string + |> required "clientId" string + |> required "connectionId" string + |> required "timestamp" int + |> required "data" decodeMessage + + +decodeMessage : Decoder DataMessage +decodeMessage = + Decode.succeed DataMessage + |> required "message" string + + + +-- { name = String +-- , id = String +-- , clientId = String +-- , connectionId = String +-- , encoding = String +-- , data = +-- { message = String +-- } +-- } + + +decodeToSocket : Nav.Key -> Value -> SocketMessageData +decodeToSocket key value = + let + result = + Decode.decodeValue + decodeSocketMessage + value + + -- |> Result.toMaybe + in + case result of + Ok obj -> + obj + + Err err -> + { name = Debug.toString err + , id = "" + , clientId = "" + , connectionId = "" + , timestamp = 0 + , data = + { message = "" + } + } + + + +-- decodeTokenData : Decoder UnwrappedTokenData +-- decodeTokenData = +-- map6 UnwrappedTokenData +-- (at [ "id" ] idDecoder) +-- (at [ "isverified" ] bool) +-- (at [ "email" ] string) +-- (at [ "firstname" ] string) +-- (at [ "verificationstring" ] verifyStringDecoder) +-- (at [ "profilepicurl" ] imageStringDecoder) + + subscriptionChanges : (Session -> msg) -> Nav.Key -> Sub msg subscriptionChanges toMsg key = onSessionChange (\val -> toMsg (decodeToSession key val)) +socketMessageChanges : (SocketMessageData -> msg) -> Nav.Key -> Sub msg +socketMessageChanges toMsg key = + publishSocketMessage (\val -> toMsg (decodeToSocket key val)) + + addHeader : Token -> Header addHeader (Token tokenString) = header "authorization" ("Token " ++ tokenString) diff --git a/src/Login.elm b/src/Login.elm index a01fd9b..a2a29b9 100755 --- a/src/Login.elm +++ b/src/Login.elm @@ -123,7 +123,7 @@ sumOfErrors model = submitLogin : Credentials -> Cmd Msg submitLogin credentials = Http.post - { url = "/.netlify/functions/login-api" + { url = "/api/login" , body = Http.jsonBody (credentialsEncoder credentials) , expect = Http.expectJson LoginDone tokenDecoder } diff --git a/src/Main.elm b/src/Main.elm index cf58e3b..2cbe583 100755 --- a/src/Main.elm +++ b/src/Main.elm @@ -2,9 +2,11 @@ module Main exposing (..) import Browser exposing (Document) import Browser.Navigation as Nav +import Chat import Credentials exposing ( Session + , SocketMessageData , UserId , VerificationString , decodeToSession @@ -13,6 +15,7 @@ import Credentials , fromTokenToString , imageStringToMaybeString , logout + , socketMessageChanges , subscriptionChanges , userIdParser , userIdToString @@ -29,6 +32,8 @@ import Login import Minidenticons exposing (identicon) import Profile import Signup +import Task +import Time import Url exposing (Url) import Url.Parser as Parser exposing ((), Parser, s) import Verification @@ -47,6 +52,7 @@ type Page | SignupPage Signup.Model | ProfilePage Profile.Model | HomePage Home.Model + | ChatPage Chat.Model | VerificationPage Verification.Model | NotFoundPage @@ -56,6 +62,7 @@ type Route | Signup | Profile UserId | Home + | Chat | Verification VerificationString | NotFound @@ -67,10 +74,13 @@ type Msg | GotLoginMsg Login.Msg | GotProfileMsg Profile.Msg | GotHomeMsg Home.Msg + | GotChatMsg Chat.Msg | GotVerificationMsg Verification.Msg | GotSubscriptionChangeMsg Session + | GotSubscriptionSocketMsg SocketMessageData | GetLogout | OpenDropdown + | ChatNewMessage content : Model -> Html Msg @@ -92,6 +102,10 @@ content model = Home.view homeModel |> Html.map GotHomeMsg + ChatPage chatModel -> + Chat.view chatModel + |> Html.map GotChatMsg + VerificationPage verificationModel -> Verification.view verificationModel |> Html.map GotVerificationMsg @@ -183,10 +197,18 @@ viewHeader { page, session, openDropdown, key } = Err err -> li [] [ text (Debug.toString err) ] + , li + [ classList + [ ( "active" + , isActive { link = Chat, page = page } + ) + ] + ] + [ a [ href "/chat" ] [ text "Chat" ] ] + , li + [] + [ a [ href "/", onClick GetLogout ] [ text "logout" ] ] ] - , li - [] - [ a [ href "/", onClick GetLogout ] [ text "logout" ] ] ] Nothing -> @@ -248,6 +270,12 @@ isActive { link, page } = ( Home, _ ) -> False + ( Chat, ChatPage _ ) -> + True + + ( Chat, _ ) -> + False + ( Verification _, VerificationPage _ ) -> True @@ -312,6 +340,18 @@ update msg model = _ -> ( model, Cmd.none ) + GotChatMsg chatMsg -> + case model.page of + ChatPage chatModel -> + let + ( chatModelFromChat, chatMsgFromChat ) = + Chat.update chatMsg chatModel + in + ( { model | page = ChatPage chatModelFromChat }, Cmd.map GotChatMsg chatMsgFromChat ) + + _ -> + ( model, Cmd.none ) + GotVerificationMsg verificationMsg -> case model.page of VerificationPage verificationModel -> @@ -351,9 +391,31 @@ update msg model = Nav.pushUrl model.key "/login" ) + GotSubscriptionSocketMsg socketMsgObj -> + let + ( chatModelFromChat, chatMsgFromChat ) = + Chat.init model.session <| Just socketMsgObj + in + ( { model | page = ChatPage chatModelFromChat }, Cmd.map GotChatMsg chatMsgFromChat ) + + -- case model.page of + -- ChatPage chatModel -> + -- let + -- ( chatModelFromChat, chatMsgFromChat ) = + -- Chat.update msgToChatMsg chatModel + -- in + -- ( { model | page = ChatPage chatModelFromChat }, Cmd.map GotChatMsg chatMsgFromChat ) + -- _ -> + -- ( model, Cmd.none ) + -- (model, GotSubscriptionSocketMsg) + -- in + -- ( model, Cmd.none ) GetLogout -> ( model, logout ) + ChatNewMessage -> + ( model, Cmd.none ) + OpenDropdown -> ( { model | openDropdown = not model.openDropdown }, Cmd.none ) @@ -365,6 +427,7 @@ matchRoute = , Parser.map Login (s "login") , Parser.map Profile (s "profile" userIdParser) , Parser.map Signup (s "signup") + , Parser.map Chat (s "chat") , Parser.map Verification (s "verify-email" verifictionStringParser) ] @@ -404,6 +467,14 @@ urlToPage url session = Nothing -> NotFoundPage + Just Chat -> + case fromSessionToToken session of + Just _ -> + ChatPage (Tuple.first (Chat.init session Nothing)) + + Nothing -> + NotFoundPage + Just Home -> HomePage (Tuple.first (Home.init ())) @@ -461,6 +532,13 @@ initCurrentPage ( url, model, existingCmds ) = in ( { model | page = HomePage pageModel }, Cmd.map GotHomeMsg pageCmds ) + ChatPage _ -> + let + ( pageModel, pageCmds ) = + Chat.init model.session Nothing + in + ( { model | page = ChatPage pageModel }, Cmd.map GotChatMsg pageCmds ) + VerificationPage _ -> let ( pageModel, pageCmds ) = @@ -490,7 +568,10 @@ init flags url key = subscriptions : Model -> Sub Msg subscriptions model = - subscriptionChanges GotSubscriptionChangeMsg model.key + Sub.batch + [ subscriptionChanges GotSubscriptionChangeMsg model.key + , socketMessageChanges GotSubscriptionSocketMsg model.key + ] main : Program Value Model Msg diff --git a/src/Profile.elm b/src/Profile.elm index 9702f7a..a461975 100644 --- a/src/Profile.elm +++ b/src/Profile.elm @@ -396,7 +396,7 @@ submitProfile session data = Http.request { method = "PUT" , headers = [ addHeader token ] - , url = "/.netlify/functions/profile-put-api" + , url = "/api/profile" , body = Http.jsonBody (profileSubmitDataEncoder data) , expect = Http.expectJson ProfileDone tokenDecoder , timeout = Nothing diff --git a/src/Signup.elm b/src/Signup.elm index 56cc5e6..66c2cd8 100755 --- a/src/Signup.elm +++ b/src/Signup.elm @@ -162,7 +162,7 @@ sumOfErrors model = submitSignup : Credentials -> Cmd Msg submitSignup credentials = Http.post - { url = "/.netlify/functions/signup-api" + { url = "/api/signup" , body = Http.jsonBody (credentialsEncoder credentials) , expect = Http.expectJson SignupDone tokenDecoder } diff --git a/src/Verification.elm b/src/Verification.elm index 6117685..e85e438 100644 --- a/src/Verification.elm +++ b/src/Verification.elm @@ -96,7 +96,7 @@ apiCallToVerify session = Http.request { method = "PUT" , headers = [ addHeader token ] - , url = "/.netlify/functions/verify-put-api" + , url = "/api/verify" , expect = Http.expectJson VerifyDone tokenDecoder , body = Http.emptyBody , timeout = Nothing