Add basic functionality

This commit is contained in:
2021-12-19 18:17:51 +02:00
parent 87c6ad9e6d
commit f259651ec9
18 changed files with 631 additions and 36 deletions

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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
// }
// }
}
]
}

6
server/views/account.pug Normal file
View File

@@ -0,0 +1,6 @@
extend templates/layout.pug
block content
.container
.center
h1 Account

View File

@@ -0,0 +1,6 @@
extend templates/layout.pug
block content
.container
.center
h1 Campaigns

View File

@@ -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 {

6
server/views/entry.pug Normal file
View File

@@ -0,0 +1,6 @@
extend templates/layout.pug
block content
.container
.center
h1= entry.name

View File

@@ -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.
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.

View File

@@ -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

17
server/views/library.pug Normal file
View File

@@ -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

View File

@@ -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")
<input type="hidden" name="username" value="#{username}"/>
.row
.col.s12
<label for="password">Password:</label>
<input id="password" type="password" name="password" autofocus/>
.col.s12
p
label
input(type="checkbox", name="rememberMe", checked="checked" )
span Remember Me
.row
<div class="col s12 center">
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
<button class="btn blue waves-effect waves-light" type="submit" name="action">Login
</button>
</div>
<div class="col s12 center">
<a class="btn-flat waves-effect waves-light" href="/account/password-recovery">forgot password</a>
</div>

View File

@@ -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
<label for="email">Email:</label>
<input id="email" type="text" name="username" value="#{data.username}" autofocus/>
.row
<div class="col s12 center">
//- <button class="btn waves-effect waves-light" v-on:click="attemptLogin">Log In</button>
<button class="btn blue waves-effect waves-light" type="submit" name="action">Login/Register
</button>
</div>
.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

6
server/views/pledges.pug Normal file
View File

@@ -0,0 +1,6 @@
extend templates/layout.pug
block content
.container
.center
h1 Pledges

View File

@@ -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