Initial commit
This commit is contained in:
23
config/default.json
Normal file
23
config/default.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"database" : {
|
||||
"dialect" : "sqlite",
|
||||
"host" : "localhost",
|
||||
"database" : "databases",
|
||||
"username" : "databases",
|
||||
"password" : "database",
|
||||
"storage" : "database.sqlite",
|
||||
"define": {
|
||||
"paranoid": true
|
||||
},
|
||||
"logging": false
|
||||
},
|
||||
"server" : {
|
||||
"secret" : "don't forget to keep your secrets secret",
|
||||
"port" : 7878,
|
||||
"cookie": {
|
||||
// "domain": "mantra.exonumia.africa"
|
||||
},
|
||||
"compiled-render": false,
|
||||
"domain": "https://mantra.exonumia.africa"
|
||||
}
|
||||
}
|
||||
14
config/test.json
Normal file
14
config/test.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"database" : {
|
||||
"dialect" : "sqlite",
|
||||
"host" : "localhost",
|
||||
"database" : "databases",
|
||||
"username" : "databases",
|
||||
"password" : "database",
|
||||
"storage" : "database.sqlite",
|
||||
"define": {
|
||||
"paranoid": true
|
||||
},
|
||||
"logging": false
|
||||
}
|
||||
}
|
||||
12
mantra.exonumia.africa.js
Normal file
12
mantra.exonumia.africa.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const db = require('mantra-db-models');
|
||||
const server = require('./server/server.js')();
|
||||
|
||||
db.init().then(() => {
|
||||
console.log("Loaded DB.");
|
||||
return server.run(db);
|
||||
}).then((port) => {
|
||||
console.log("Server is running on: ", port);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to run server: ", error);
|
||||
})
|
||||
3201
package-lock.json
generated
Normal file
3201
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "mantra",
|
||||
"version": "0.0.1",
|
||||
"description": "Community platform for translating literature.",
|
||||
"main": "mantra.exonumia.africa.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/exonumia-cc/mantra.exonumia.africa.git"
|
||||
},
|
||||
"author": "kngako",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/exonumia-cc/mantra.exonumia.africa/issues"
|
||||
},
|
||||
"homepage": "https://github.com/exonumia-cc/mantra.exonumia.africa#readme",
|
||||
"devDependencies": {
|
||||
"sqlite3": "npm:@vscode/sqlite3@^5.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.1",
|
||||
"config": "^3.3.6",
|
||||
"connect-session-sequelize": "^7.1.2",
|
||||
"express": "^4.17.2",
|
||||
"express-session": "^1.17.2",
|
||||
"mantra-db-models": "file:../mantra-db-models",
|
||||
"passport": "^0.5.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"pug": "^3.0.2"
|
||||
}
|
||||
}
|
||||
21
server/router/index.js
Normal file
21
server/router/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* This router handles things related to the web browser experience...
|
||||
*/
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
module.exports = function (options) {
|
||||
var express = options.express;
|
||||
var db = options.db;
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.get(function(request, response, next) {
|
||||
response.display("home", {
|
||||
user: request.user,
|
||||
pageTitle: "Home - Mantra"
|
||||
})
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
268
server/server.js
Normal file
268
server/server.js
Normal file
@@ -0,0 +1,268 @@
|
||||
const config = require('config');
|
||||
const express = require('express');
|
||||
const pug = require("pug");
|
||||
const session = require("express-session");
|
||||
const SequelizeSessionStore = require('connect-session-sequelize')(session.Store);
|
||||
const bodyParser = require("body-parser");
|
||||
const path = require("path");
|
||||
|
||||
const passport = require('passport');
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = function () {
|
||||
var server = {};
|
||||
|
||||
server.run = (db) => {
|
||||
console.log("Starting Server")
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set("view engine", "pug");
|
||||
app.set("views", path.resolve("server/views"));
|
||||
|
||||
// compile pug views below so that they are rendered quicker
|
||||
var pugPages = {};
|
||||
|
||||
var modelDir = path.join(__dirname, './views');
|
||||
fs.readdirSync(modelDir)
|
||||
.filter(file => {
|
||||
return file.endsWith(".pug");
|
||||
})
|
||||
.forEach(file => {
|
||||
const name = file.split(".pug")[0];
|
||||
|
||||
pugPages[name] = pug.compileFile(path.join(modelDir, file))
|
||||
});
|
||||
|
||||
// Set up middleware to make use of the compiled pugjs views
|
||||
app.use((request, response, next) => {
|
||||
// This is cheating...
|
||||
response.display = (view, options) => {
|
||||
options.domain = config.get("server.domain");
|
||||
options.urlEndpoint = request.originalUrl;
|
||||
// Set a default pageTitle...
|
||||
options.pageTitle = options.pageTitle ? options.pageTitle : "Mantra";
|
||||
options.errorMessage = (error) => {
|
||||
// TODO: Make error human readable...
|
||||
return error.message;
|
||||
}
|
||||
if(config.get("server.compiled-render")) {
|
||||
if(pugPages[view]) {
|
||||
response.send(pugPages[view](options))
|
||||
} else {
|
||||
response.send(pugPages["404"](options))
|
||||
}
|
||||
} else {
|
||||
response.render(view, options);
|
||||
}
|
||||
}
|
||||
next();
|
||||
})
|
||||
app.use('/static', express.static("server/static"));
|
||||
|
||||
// Session related stuff...
|
||||
function extendDefaultFields(defaults, session) {
|
||||
return {
|
||||
data: defaults.data,
|
||||
expires: session.cookie && session.cookie.expires ? session.cookie.expires : defaults.expires, // 157680000
|
||||
userId: session.passport.user
|
||||
};
|
||||
}
|
||||
|
||||
var sessionStore = new SequelizeSessionStore({
|
||||
db: db.sequelize,
|
||||
table: 'session',
|
||||
extendDefaultFields: extendDefaultFields
|
||||
// TODO: Define expiry and clean up...
|
||||
});
|
||||
|
||||
app.use(session({
|
||||
store: sessionStore,
|
||||
secret: config.get("server.secret"),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: config.get("server.cookie")
|
||||
}));
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
|
||||
passport.use(new LocalStrategy(
|
||||
function(username, password, done) {
|
||||
username = username != null ? username.trim().toLowerCase() : "";
|
||||
password = password != null ? password : "";
|
||||
return db.User.findOne({
|
||||
include: [
|
||||
{
|
||||
association: db.User.Passwords
|
||||
},
|
||||
{
|
||||
association: db.User.UserEmails,
|
||||
required: true,
|
||||
where: {
|
||||
emailAddress: {
|
||||
[db.Sequelize.Op.like]: username
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
association: db.User.UserRoles,
|
||||
// required: true,
|
||||
include: [
|
||||
{
|
||||
association: db.UserRole.Role,
|
||||
required: true,
|
||||
where: {
|
||||
type: {
|
||||
[db.Sequelize.Op.or]: roles
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(
|
||||
user => {
|
||||
// Validate Password
|
||||
if(user) {
|
||||
if (!user.passwords[0].validPassword(password)) {
|
||||
return done(null, false, {
|
||||
username: username,
|
||||
message: 'Invalid username or password.'
|
||||
});
|
||||
} else {
|
||||
return done(null, user);
|
||||
}
|
||||
} else {
|
||||
console.error("User Doesn't Exist: ", username);
|
||||
return done(null, false, {
|
||||
username: username,
|
||||
message: 'Invalid username or password.'
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
err => {
|
||||
console.error("Authentication Failed: " + err);
|
||||
|
||||
return done(null, false, { message: 'Incorrect username or password.' });
|
||||
}
|
||||
).catch(error => {
|
||||
console.log("Passport Error: ", error);
|
||||
return done(error);
|
||||
})
|
||||
}
|
||||
));
|
||||
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, done) {
|
||||
// TODO: Add memberships and things like that...
|
||||
return db.User.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
association: db.User.UserRoles,
|
||||
// required: true,
|
||||
include: [
|
||||
{
|
||||
association: db.UserRole.Role,
|
||||
required: true,
|
||||
where: {
|
||||
type: {
|
||||
[db.Sequelize.Op.or]: roles
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(user => {
|
||||
return done(null, user);
|
||||
}).catch(error => {
|
||||
return done(error);
|
||||
})
|
||||
});
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
sessionStore.sync();
|
||||
|
||||
// TODO: Create a load router module...
|
||||
var router = require('./router/index.js')({
|
||||
app: app,
|
||||
express: express,
|
||||
db: db,
|
||||
passport: passport
|
||||
});
|
||||
app.use('/', router);
|
||||
|
||||
// 404 pages...
|
||||
app.use((request, response, next) => {
|
||||
|
||||
if(!response.headersSent) {
|
||||
response.status(404);
|
||||
|
||||
if (request.accepts('html')) {
|
||||
response.display("404", {
|
||||
user: request.user,
|
||||
pageTitle: "Page Not Found - Mantra"
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.accepts('application/json')) {
|
||||
response.json({
|
||||
message: "URL not accessible"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
response.type('txt').send('Resource Not found');
|
||||
}
|
||||
});
|
||||
|
||||
// 500 pages...
|
||||
app.use((error, request, response, next) => {
|
||||
console.error(`${response.getHeader("id")} - `, error);
|
||||
if(error && !response.headersSent) {
|
||||
response.status(500);
|
||||
|
||||
// TODO: Check if production and then show stack...
|
||||
if (request.accepts('html')) {
|
||||
response.display("error", {
|
||||
user: request.user,
|
||||
pageTitle: "Internal Error - Mantra",
|
||||
error: error
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.xhr) {
|
||||
response.json({
|
||||
message: "Internal Error, Please check your tires.",
|
||||
error: error
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
response.type('txt').send('Internal server error');
|
||||
}
|
||||
});
|
||||
|
||||
const port = process.env.PORT || config.get("server.port");
|
||||
|
||||
app.listen(port);
|
||||
resolve(port);
|
||||
});
|
||||
}
|
||||
|
||||
return server;
|
||||
};
|
||||
9067
server/static/css/materialize.css
vendored
Normal file
9067
server/static/css/materialize.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13
server/static/css/materialize.min.css
vendored
Normal file
13
server/static/css/materialize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
server/static/img/panther.jpg
Normal file
BIN
server/static/img/panther.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
12374
server/static/js/materialize.js
vendored
Normal file
12374
server/static/js/materialize.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
server/static/js/materialize.min.js
vendored
Normal file
6
server/static/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
server/views/404.pug
Normal file
6
server/views/404.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 404: Page Not Found
|
||||
20
server/views/css/site-layout.css
Normal file
20
server/views/css/site-layout.css
Normal file
@@ -0,0 +1,20 @@
|
||||
body {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 992px) {
|
||||
header, main, footer {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
47
server/views/css/site.css
Normal file
47
server/views/css/site.css
Normal file
@@ -0,0 +1,47 @@
|
||||
/* Color scheme from https://material.io/resources/color/#!/?view.left=0&view.right=0&primary.color=212121&secondary.color=263238 */
|
||||
.primary-color {
|
||||
background-color: #212121 !important;
|
||||
}
|
||||
|
||||
.primary-color-light {
|
||||
background-color: #484848 !important;
|
||||
}
|
||||
|
||||
.primary-color-dark {
|
||||
background-color: #000000 !important;
|
||||
}
|
||||
|
||||
.secondary-color {
|
||||
background-color: #263238 !important;
|
||||
}
|
||||
|
||||
.secondary-color-light {
|
||||
background-color: #4f5b62 !important;
|
||||
}
|
||||
|
||||
.secondary-color-dark {
|
||||
background-color: #000a12 !important;
|
||||
}
|
||||
|
||||
.money-chip {
|
||||
display: inline-block;
|
||||
/* height: 33px;
|
||||
font-size: 1;
|
||||
font-weight: 500; */
|
||||
line-height: 32px;
|
||||
padding: 12px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.auto-image {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
/* This gives use dots in our markdown lists... */
|
||||
.flow-text ul li {
|
||||
list-style-type: disc !important;
|
||||
}
|
||||
|
||||
.bitcoin-address {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
6
server/views/error.pug
Normal file
6
server/views/error.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Internal Server Error
|
||||
6
server/views/home.pug
Normal file
6
server/views/home.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extend templates/layout.pug
|
||||
|
||||
block content
|
||||
.container
|
||||
.center
|
||||
h1 Home
|
||||
18
server/views/includes/pagination.pug
Normal file
18
server/views/includes/pagination.pug
Normal file
@@ -0,0 +1,18 @@
|
||||
ul.pagination
|
||||
- var disableLeft = page<=0 ? "disabled" : "";
|
||||
- var hrefLeft = page<=0 ? "#" : "?page="+(Number(page)-1);
|
||||
li(class=disableLeft)
|
||||
//- TODO: Compute the left value...
|
||||
a(href=hrefLeft)
|
||||
i.material-icons chevron_left
|
||||
- var n = 0;
|
||||
while n < totalPages
|
||||
- var derivedClass = n == page ? "active" : ""
|
||||
li(class=derivedClass)
|
||||
a(href="?page="+n)= (++n)
|
||||
- var disableRight = page>=(Number(totalPages)-1) ? "disabled" : "";
|
||||
li(class=disableRight)
|
||||
//- TODO: Compute the right value..
|
||||
- var hrefRight = page>=(Number(totalPages)-1) ? "#" : "?page="+(Number(page)+1);
|
||||
a(href=hrefRight)
|
||||
i.material-icons chevron_right
|
||||
4
server/views/js/site.js
Normal file
4
server/views/js/site.js
Normal file
@@ -0,0 +1,4 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
//
|
||||
M.Sidenav.init(document.querySelectorAll('.sidenav'));
|
||||
});
|
||||
122
server/views/templates/layout.pug
Normal file
122
server/views/templates/layout.pug
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
doctype html
|
||||
html(lang="en" dir="ltr")
|
||||
head
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
||||
<link async href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
link(rel="icon", href="/static/img/mantra-icon.png", type="image/png")
|
||||
|
||||
block seo
|
||||
title Mantra
|
||||
|
||||
//- Using Material Design Lite for the CSS stuff
|
||||
//- https://getmdl.io
|
||||
//- <script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
|
||||
//- <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
|
||||
link(rel="stylesheet", href="/static/css/materialize.min.css")
|
||||
style(media="screen")
|
||||
include ../css/site-layout.css
|
||||
style(media="screen")
|
||||
include ../css/site.css
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
block additionalStyle
|
||||
body
|
||||
ul.sidenav.sidenav-fixed#slide-out
|
||||
li
|
||||
.user-view.primary-color-light
|
||||
.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
|
||||
|
||||
if user
|
||||
li
|
||||
.divider
|
||||
li
|
||||
a(href="/account")
|
||||
i.material-icons.black-text person
|
||||
span account
|
||||
li
|
||||
a(href="/account/tokens")
|
||||
i.material-icons.black-text vpn_key
|
||||
span tokens
|
||||
li
|
||||
form.center(action="/account/logout" method="POST")
|
||||
<button class="btn-flat" type="submit">logout</button>
|
||||
//- li.red-text
|
||||
a DELETE ACCOUNT
|
||||
else
|
||||
li
|
||||
a(href="/account/authenticate") login/register
|
||||
//- li
|
||||
a(href="/login/hd-auth") login with hd-auth
|
||||
|
||||
header(role="banner")
|
||||
block navigation
|
||||
nav.primary-color
|
||||
.nav-wrapper
|
||||
a.brand-logo.center.hide-on-small-only(href="/") Mantra
|
||||
ul.left
|
||||
li
|
||||
a.sidenav-trigger(href="#", data-target="slide-out")
|
||||
i.material-icons menu
|
||||
|
||||
main(role="main")
|
||||
.row
|
||||
.col.s12
|
||||
//- I just need the space at the top of the page to be good on mobile...
|
||||
block content
|
||||
.container
|
||||
p.flow-text Center content
|
||||
|
||||
.row
|
||||
.col.s12
|
||||
//- I just need some space at the bottom...
|
||||
|
||||
block footer
|
||||
footer.primary-color.page-footer(role="footer")
|
||||
.container
|
||||
.row
|
||||
.col.s12.m6
|
||||
p.flow-text Follow
|
||||
ul
|
||||
li
|
||||
a.white-text(href="https://www.twitter.com/exonumia280") Twitter
|
||||
li
|
||||
a.white-text(href="https://www.facebook.com/exonumia.africa") Facebook
|
||||
li
|
||||
a.white-text(href="https://www.instagram.com/exonumia.africa") Instagram
|
||||
li
|
||||
a.white-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
|
||||
li
|
||||
a.white-text(href="/privacy") Privacy Policy
|
||||
li
|
||||
a.white-text(href="/compliance") Compliance
|
||||
li
|
||||
a.white-text(href="/terms") Terms
|
||||
li
|
||||
a.white-text(href="/contact") Contact
|
||||
|
||||
.footer-copyright.primary-color-dark
|
||||
.container
|
||||
span © 2021
|
||||
a(href="https://sigidli.com/")
|
||||
strong Sigidli
|
||||
|
||||
|
||||
script(src="/static/js/materialize.min.js")
|
||||
script
|
||||
include ../js/site.js
|
||||
block additionalScripts
|
||||
//- All other scripts will be added here as they are needed in the views
|
||||
Reference in New Issue
Block a user