diff --git a/server/router/account/index.js b/server/router/account/index.js new file mode 100644 index 0000000..6b516a1 --- /dev/null +++ b/server/router/account/index.js @@ -0,0 +1,126 @@ +/** + * This router handles things related to the web browser experience... + */ + +// This is the mock data we working with... +module.exports = function (options) { + var express = options.express; + var db = options.db; + var passport = options.passport; + + var router = express.Router(); + + router.route('/') + .get(function(request, response, next) { + if (request.user) { + response.display("account", { + user: request.user, + pageTitle: "Home - Mantra" + }) + } else { + next(); + } + }); + + router.route('/authenticate') // TODO: Might have a unique identifier for this endpoint... + .get(function (request, response, next) { + if (request.user == null) { + response.display("login-username", { + user: request.user, + pageTitle: "Login - Mantra", + }); + } else { + response.redirect('/'); + } + }) + .post(function (request, response, next) { + if (request.user == null) { + var username = "" + request.body.username; + username = username.trim().toLowerCase(); + if(request.body.password == null) { + const email = db.Email.build({ + address: username + }) + email.validate() + .then((email)=> { + response.display("login-password", { + user: request.user, + username: email.address, + pageTitle: "Login - Mantra", + }); + }).catch(error => { + response.display("login-username", { + user: request.user, + username: username, + pageTitle: "Login - Mantra", + data: { + message: "Enter a valid email" + } + }); + }) + } else { + passport.authenticate('local', (error, user, info) => { + console.error("Error: ", error) + if(info) { + console.log("Info: ", info) + response.display("login-username", { + user: request.user, + username: username, + pageTitle: "Login - Mantra", + data: info + }) + } else if(user) { + + if(request.body.rememberMe) { + var now = new Date(); + var expiresAt = new Date(); + expiresAt.setFullYear(expiresAt.getFullYear()+5); + // request.session.cookie.expires = expiresAt; + request.session.cookie.maxAge = expiresAt; + + // TODO: Add the remember me cookie... + } else { + // TODO: Set that the remember me cookie wasn't selected... + } + + request.logIn(user, function(err) { + if (err) { return next(err); } + return response.redirect('/'); + }); + } else { + response.redirect('/account/authenticate') + } + })(request, response, next); + } + } else { + response.redirect("/"); + } + }) + + router.route('/logout') + .post(function(request, response, next) { + if (request.user) { + request.logout(); + return db.Session.destroy({ + where: { + sid: request.sessionID + } + }).then(result => { + response.redirect('/'); + }).catch(error => { + response.redirect('/'); + console.error("Log out error: ", error); + }) + } else { + response.redirect('/'); + } + }); + + // var passwordResetRouter = require('./password-reset/')(options); + var libraryRouter = require('../library')(options); + + // router.use('/password-reset', passwordResetRouter); + router.use('/library', libraryRouter); + + return router; +}; \ No newline at end of file diff --git a/server/router/account/password-reset/index.js b/server/router/account/password-reset/index.js new file mode 100644 index 0000000..590867e --- /dev/null +++ b/server/router/account/password-reset/index.js @@ -0,0 +1,182 @@ +const express = require('express') +const emailAPI = require('../../../email/email-api') + +module.exports = function (options) { + const db = options.db; + var router = express.Router(); + + router.route('/') + .get(function(request, response, next) { + response.display("password-reset-initiation", { + user: request.user, + pageTitle: "Password Reset - Mantra" + }) + }).post(function(request, response, next) { + // TODO: Create UserPasswordReset object. + // TODO: Send email with link to /account/password-reset/:id + db.User.findOne({ + include: [ + { + association: db.User.UserEmails, + required: true, + where: { + emailAddress: request.body.email + } + }, + { + association: db.User.UserPasswordReset + } + // TODO: Include user.UserPasswordReset + ] + }).then(user => { + if (user) { + // TODO: Check if user already created a password reset entry to save on a db transaction + return Promise.all([ + user.userEmails[0], + user.userPasswordReset ? user.userPasswordReset : db.UserPasswordReset.create({ + userId: user.id + }) + ]) + } else { + return [null, null] + } + }).then(async (results) => { + userEmail = results[0] + userPasswordReset = results[1] + if (userPasswordReset) { + await userPasswordReset.increment('sent') + // Send password reset email... + return emailAPI.sendPasswordResetEmail(userPasswordReset.token, userEmail.emailAddress) + + } else { + return null + } + }).then(emailResult => { + if (emailResult) { + response.display("password-reset-initiated", { + user: request.user, + pageTitle: "Password Reset Initiated - Mantra" + }) + } else { + // TODO: Check if email is an immigrant + db.Immigrant.findOne({ + where: { + emailAddress: request.body.email + }, + include: [ + { + association: db.Immigrant.User, + }, + { + association: db.Immigrant.Email, + required: true, + } + ] + }).then(async (immigrant) => { + if (immigrant && immigrant.user == null) { + // Resend invitation + await emailAPI.sendImmigrationInvitationEmail( + immigrant.id, immigrant.emailAddress, immigrant.displayName + ) + + response.display("password-reset-initiated", { + user: request.user, + pageTitle: "Password Reset Initiated - Mantra" + }) + } else { + next(`Failed to create a user password reset entry (user ${request.body.email} doesn't exist)`) + } + }).catch (error => { + next(error) + }) + // TODO: Create password reset error page with possible reasons... + + } + }).catch(reason => { + console.error("Something went wrong with reset: ", reason) + // TODO: Tell user + next(reason) + }) + + }) + + router.route('/:token') + .get(function(request, response, next) { + if (request.user) { + // TODO: + response.display("logout-to-continue", { + user: request.user, + pageTitle: "Logout Before Continue - Mantra" + }) + } else { + db.UserPasswordReset.findByPk(request.params.token, { + include: [ + { + association: db.UserPasswordReset.User + } + ] + }) + .then(userPasswordReset => { + if (userPasswordReset) { + response.display("password-reset", { + user: request.user, + pageTitle: "Password Reset - Mantra", + userPasswordReset: userPasswordReset + }) + } else { + next() + } + }).catch(error => { + next(error) + }) + } + + }).post(function(request, response, next) { + if (Boolean(request.body.password) && request.body.password == request.body.confirmPassword) { + db.UserPasswordReset.findByPk(request.params.token, { + include: [ + { + association: db.UserPasswordReset.User, + required: true, + include: [ + { + association: db.User.Passwords, + required: true + } + ] + } + ] + }).then(userPasswordReset => { + if (userPasswordReset) { + var password = userPasswordReset.user.passwords[0] + password.password = request.body.password // Password will be hashed... + return Promise.all([userPasswordReset, password.save()]) + } else { + return [null, null] + } + }).then(results => { + userPasswordReset = results[0] + password = results[1] + if (password) { + // Password reset success invite user to reset... + response.redirect("/account") + // Cleanup... + userPasswordReset.destroy({ + force: true + }).then(() => {}) + .catch(reason => console.error("Failed to destroy userPasswordReset", reason)) + } else { + // TODO: Give user failed password page... + next("Failed to reset password") + } + }).catch(error => { + next(error) + }) + } else { + // TODO: Tell user password reset failed.. + next("Passwords do not match") + } + + }) + return router; +}; \ No newline at end of file diff --git a/server/router/campaigns/index.js b/server/router/campaigns/index.js new file mode 100644 index 0000000..70532fa --- /dev/null +++ b/server/router/campaigns/index.js @@ -0,0 +1,15 @@ +const express = require('express') + +module.exports = function (options) { + const db = options.db; + var router = express.Router(); + + router.route('/') + .get(function(request, response, next) { + response.display("campaigns", { + user: request.user, + pageTitle: "Library - Mantra" + }) + }) + return router; +}; \ No newline at end of file diff --git a/server/router/index.js b/server/router/index.js index d9409bb..51238ad 100644 --- a/server/router/index.js +++ b/server/router/index.js @@ -17,5 +17,15 @@ module.exports = function (options) { }) }); + var accountRouter = require('./account/')(options); + var libraryRouter = require('./library')(options); + var pledgesRouter = require('./pledges/')(options); + var campaignsRouter = require('./campaigns/')(options); + + router.use('/library', libraryRouter); + router.use('/pledges', pledgesRouter); + router.use('/campaigns', campaignsRouter); + router.use('/account', accountRouter); + return router; }; \ No newline at end of file diff --git a/server/router/library/index.js b/server/router/library/index.js new file mode 100644 index 0000000..8ae73cd --- /dev/null +++ b/server/router/library/index.js @@ -0,0 +1,88 @@ +const express = require('express'); +const req = require('express/lib/request'); + +module.exports = function (options) { + const db = options.db; + var router = express.Router(); + + router.route('/') + .get(function(request, response, next) { + db.Entry.findAll({ + + }).then(entries => { + response.display("library", { + user: request.user, + pageTitle: "Library - Mantra", + entries: entries + }) + }).catch(error => { + next(error) + }) + }) + + router.route('/add') + .get(function(request, response, next) { + if (request.user) { + response.display("library-form", { + user: request.user, + pageTitle: "Add Library - Mantra", + entry: { } + }) + } else { + next() + } + }) + .post(function(request, response, next) { + if (request.user) { + db.Dialect.findOne({ + where: { + languageId: "en", + countryId: "int" + } + }).then(dialect => { + if (dialect) { + return db.Entry.create({ + name: request.body.name, + url: request.body.url, + dialectId: dialect.id, + licenseId: "copyright" + }) + } else { + response.redirect("/library/add") // TODO: Show error message on missing dialect... + } + + }).then(entry => { + if (entry) { + response.redirect(`/library/${entry.id}`) + } else { + next() + } + }).catch(error => { + next(error) + }) + } else { + next() + } + }) + + + router.route('/:id') + .get(function(request, response, next) { + db.Entry.findByPk(request.params.id, { + include: [ + { + association: db.Entry.EntryApproval + } + ] + }).then(entry => { + response.display("entry", { + user: request.user, + pageTitle: "Library - Mantra", + entry: entry + }) + }).catch(error => { + next(error) + }) + }) + return router; +}; \ No newline at end of file diff --git a/server/router/pledges/index.js b/server/router/pledges/index.js new file mode 100644 index 0000000..94b9390 --- /dev/null +++ b/server/router/pledges/index.js @@ -0,0 +1,15 @@ +const express = require('express') + +module.exports = function (options) { + const db = options.db; + var router = express.Router(); + + router.route('/') + .get(function(request, response, next) { + response.display("pledges", { + user: request.user, + pageTitle: "Library - Mantra" + }) + }) + return router; +}; \ No newline at end of file diff --git a/server/server.js b/server/server.js index 5b82c71..fd78c8d 100644 --- a/server/server.js +++ b/server/server.js @@ -115,12 +115,12 @@ module.exports = function () { include: [ { association: db.UserRole.Role, - required: true, - where: { - type: { - [db.Sequelize.Op.or]: roles - } - } + // required: true, + // where: { + // type: { + // [db.Sequelize.Op.or]: roles + // } + // } } ] } @@ -172,12 +172,12 @@ module.exports = function () { include: [ { association: db.UserRole.Role, - required: true, - where: { - type: { - [db.Sequelize.Op.or]: roles - } - } + // required: true, + // where: { + // type: { + // [db.Sequelize.Op.or]: roles + // } + // } } ] } diff --git a/server/views/account.pug b/server/views/account.pug new file mode 100644 index 0000000..72734b1 --- /dev/null +++ b/server/views/account.pug @@ -0,0 +1,6 @@ +extend templates/layout.pug + +block content + .container + .center + h1 Account \ No newline at end of file diff --git a/server/views/campaigns.pug b/server/views/campaigns.pug new file mode 100644 index 0000000..e00a68e --- /dev/null +++ b/server/views/campaigns.pug @@ -0,0 +1,6 @@ +extend templates/layout.pug + +block content + .container + .center + h1 Campaigns \ No newline at end of file diff --git a/server/views/css/site.css b/server/views/css/site.css index 6d4c293..591a7eb 100644 --- a/server/views/css/site.css +++ b/server/views/css/site.css @@ -1,14 +1,18 @@ +html { + font-family: courier; +} + /* Color scheme from https://material.io/resources/color/#!/?view.left=0&view.right=1&primary.color=212121&secondary.color=263238 */ .primary-color { - background-color: #683400 !important; + background-color: #f9fbff !important; } .primary-color-light { - background-color: #9a5e2c !important; + background-color: #ffffff !important; } .primary-color-dark { - background-color: #3d0b00 !important; + background-color: #c6c8cc !important; } .secondary-color { diff --git a/server/views/entry.pug b/server/views/entry.pug new file mode 100644 index 0000000..5507bbd --- /dev/null +++ b/server/views/entry.pug @@ -0,0 +1,6 @@ +extend templates/layout.pug + +block content + .container + .center + h1= entry.name \ No newline at end of file diff --git a/server/views/home.pug b/server/views/home.pug index 5700894..ae9dbe4 100644 --- a/server/views/home.pug +++ b/server/views/home.pug @@ -7,6 +7,6 @@ block content p.flow-text A home for translation projects. p.flow-text Give translators the abilitiy to crowd fund for their work. - p.flow-text Gives authors/publishers the option to crowd fund for rights to their literature. - p.flow-text Give the community the ability to bid for translations of their choice. - p.flow-text Open source by default. \ No newline at end of file + p.flow-text Gives authors/publishers the option to crowd fund digital rights to their literature. + p.flow-text Give the community the ability to pledge for library of their choice. + p.flow-text Open source open literature. \ No newline at end of file diff --git a/server/views/library-form.pug b/server/views/library-form.pug new file mode 100644 index 0000000..1eab824 --- /dev/null +++ b/server/views/library-form.pug @@ -0,0 +1,18 @@ +extend templates/layout.pug + +block content + .container + .center + h1 Library + + .row + .col.s12 + form.row(action="/library/add", method="post") + .col.s12.input-field + input#name(type="text", name="name", value=entry.name) + label(for="name") Entry Name + .col.s12.input-field + input#url(type="text", name="url", value=entry.url) + label(for="url") Entry Url + .col.s12 + button.btn.black(type="submit") add entry \ No newline at end of file diff --git a/server/views/library.pug b/server/views/library.pug new file mode 100644 index 0000000..d193766 --- /dev/null +++ b/server/views/library.pug @@ -0,0 +1,17 @@ +extend templates/layout.pug + +block content + .container + .center + h1 Library + + a.btn.black(href="/library/add") add entry + if entries.length == 0 + p.flow-text No Literature currently on the system + else + .row + each entry in entries + .col.s12 + a(href=`/library/${entry.id}`) + .card-panel + p.flow-text= entry.name \ No newline at end of file diff --git a/server/views/login-password.pug b/server/views/login-password.pug new file mode 100644 index 0000000..43b668b --- /dev/null +++ b/server/views/login-password.pug @@ -0,0 +1,39 @@ +extend templates/layout.pug + +block content + - + if (data) + var message = data.message + data = data == null ? {} : data; + .container + .row + .col.s12.l4.m3 + p + .col.s12.l4.m6 + .center + h2.flow-text Password + if message + p.flow-text.red-text= message + form(action="/account/authenticate" method="POST") + + .row + .col.s12 + + + .col.s12 + p + label + input(type="checkbox", name="rememberMe", checked="checked" ) + span Remember Me + .row + +
+ //- + +
+ +
+ forgot password +
+ diff --git a/server/views/login-username.pug b/server/views/login-username.pug new file mode 100644 index 0000000..997e66e --- /dev/null +++ b/server/views/login-username.pug @@ -0,0 +1,44 @@ +extend templates/layout.pug + +block content + - + if (data) + var message = data.message + data = data == null ? {} : data; + + .container + .row + .col.s12 + .row + .col.s12.l4.m3 + p + .col.s12.l4.m6 + .center + h2.flow-text Login/Register + if message + p.flow-text.red-text= message + form(action="/account/authenticate" method="POST") + .row + + + .row + +
+ //- + +
+ .col.s12 + p.center + a.center(href="/account/password-reset") forgot password + p.center + a.center(href="/account/creation") create a new account + .col.s12.l4.m3 + .row + + //- .col.s12 + .center + p + strong OR + a.btn.blue(href="/account/oauth/google") LOGIN VIA GOOGLE + \ No newline at end of file diff --git a/server/views/pledges.pug b/server/views/pledges.pug new file mode 100644 index 0000000..9167262 --- /dev/null +++ b/server/views/pledges.pug @@ -0,0 +1,6 @@ +extend templates/layout.pug + +block content + .container + .center + h1 Pledges \ No newline at end of file diff --git a/server/views/templates/layout.pug b/server/views/templates/layout.pug index f6bced6..8e692e0 100644 --- a/server/views/templates/layout.pug +++ b/server/views/templates/layout.pug @@ -25,16 +25,29 @@ html(lang="en" dir="ltr") body ul.sidenav.sidenav-fixed#slide-out li - .user-view.primary-color-light + .user-view.primary-color-light.z-depth-2 .background a#name - p.flow-text.white-text.name Mantra - a#bio - span.white-text Mantra Mantra Mantra - a#email - span.white-text.email mantra@exonumia.africa + p.flow-text.black-text.name Mantra. + //- a(href="#bio") + span.black-text Open source open literature + a(href="#email") + span.email mantra@exonumia.africa if user + li + a(href="/library") + i.material-icons.black-text person + span library + li + a(href="/campaigns") + i.material-icons.black-text person + span campaigns + + li + a(href="/pledges") + i.material-icons.black-text person + span pledges li .divider li @@ -60,7 +73,7 @@ html(lang="en" dir="ltr") block navigation nav.primary-color .nav-wrapper - a.brand-logo.center.hide-on-small-only(href="/") Mantra + a.brand-logo.black-text.center.hide-on-small-only(href="/") Mantra ul.left li a.sidenav-trigger(href="#", data-target="slide-out") @@ -79,34 +92,34 @@ html(lang="en" dir="ltr") //- I just need some space at the bottom... block footer - footer.primary-color.page-footer(role="footer") + footer.primary-color.page-footer.z-depth-3(role="footer") .container .row .col.s12.m6 - p.flow-text Follow + p.flow-text.black-text Follow ul li - a.white-text(href="https://www.twitter.com/exonumia280") Twitter + a.black-text(href="https://www.twitter.com/exonumia280") Twitter li - a.white-text(href="https://www.facebook.com/exonumia.africa") Facebook + a.black-text(href="https://www.facebook.com/exonumia.africa") Facebook li - a.white-text(href="https://www.instagram.com/exonumia.africa") Instagram + a.black-text(href="https://www.instagram.com/exonumia.africa") Instagram li - a.white-text(href="https://www.linkedin.com/company/exonumia-africa") LinkedIn + a.black-text(href="https://www.linkedin.com/company/exonumia-africa") LinkedIn .col.s12.m6 //- p.flow-text Links //- ul li - a.white-text(href="/faq") FAQ + a.black-text(href="/faq") FAQ li - a.white-text(href="/privacy") Privacy Policy + a.black-text(href="/privacy") Privacy Policy li - a.white-text(href="/compliance") Compliance + a.black-text(href="/compliance") Compliance li - a.white-text(href="/terms") Terms + a.black-text(href="/terms") Terms li - a.white-text(href="/contact") Contact + a.black-text(href="/contact") Contact .footer-copyright.primary-color-dark .container