mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-09-10 18:07:18 -05:00
Stripped down and reworked pages
Stripped down the website to remove account managment and API handling. These features may be brought back in the future when the network goes live. A few new pages such as the Cemu and FAQ pages were added along with a finished credits list
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
body.flex.center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
body footer {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
body footer .content {
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
padding: 1em 0;
|
||||
box-shadow: none;
|
||||
width: 30rem;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
/* login/register card */
|
||||
.card {
|
||||
background-color: white;
|
||||
box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07);
|
||||
width: 35rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
.card .content {
|
||||
padding: 0 3em 4em 3em;
|
||||
}
|
||||
.card .head {
|
||||
background-color: #673ab7;
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
padding: 1.5em 2em;
|
||||
margin: 0;
|
||||
}
|
||||
.card .content .btnMargin {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 3em 0 0 0;
|
||||
}
|
||||
.card .content .lowText {
|
||||
text-align: right;
|
||||
color: #707070;
|
||||
}
|
||||
@@ -91,7 +91,8 @@ nav.navWrapper .navProfile {
|
||||
}
|
||||
nav.navWrapper .navProfile img {
|
||||
border-radius: 50%;
|
||||
width: 2em; height: 2em;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
@@ -156,7 +157,8 @@ nav.navWrapper.open .navItem.hamburger {
|
||||
/* content wrapper */
|
||||
.contentWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
nav.navWrapper ~ .contentWrapper:nth-child(2) {
|
||||
margin-top: 3rem;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.input-container > * {
|
||||
flex: 1;
|
||||
}
|
||||
.input-container > *:first-child {
|
||||
margin-right: 3em;
|
||||
}
|
||||
@media screen and (max-width: 1000px) {
|
||||
.input-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
29
assets/css/pretendo-faq.css
Normal file
29
assets/css/pretendo-faq.css
Normal file
@@ -0,0 +1,29 @@
|
||||
::-webkit-details-marker {
|
||||
float: right;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
details {
|
||||
background: #f6f6f6;
|
||||
border-radius: 7px;
|
||||
margin-top: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details[open] summary {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
details summary {
|
||||
border-radius: 7px;
|
||||
padding: 10px 20px;
|
||||
background: #673db6;
|
||||
color: white;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
details .text {
|
||||
margin-left: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/* news cards */
|
||||
.smallCard {
|
||||
padding: 2em;
|
||||
border: 1px solid rgb(165, 165, 165);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.07);
|
||||
position: relative;
|
||||
margin: .5em;
|
||||
width: calc(50% - 1em );
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
}
|
||||
.smallCard h3 {
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
}
|
||||
.smallCard a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
.smallCard {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
body .contentWrapper.userPage {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin: 10rem 0 7rem 0 !important; /*im sorry, I had to*/
|
||||
}
|
||||
body footer {
|
||||
padding: 0;
|
||||
box-shadow: 0 -3px 10px 0px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
body footer .content {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* content card */
|
||||
.contentCard {
|
||||
background-color: white;
|
||||
box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07);
|
||||
box-sizing: border-box;
|
||||
color: #707070;
|
||||
width: 40rem;
|
||||
max-width: 100vw;
|
||||
padding: 3rem 4rem;
|
||||
}
|
||||
.infoContainer.hide {
|
||||
display: none;
|
||||
}
|
||||
.infoList {
|
||||
display: block;
|
||||
padding: 1em 0;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
}
|
||||
.infoList span {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
color: #707070;
|
||||
width: 6em;
|
||||
}
|
||||
.infoList + .infoList {
|
||||
border-top: 1px solid #afafaf;
|
||||
}
|
||||
.infoContainer .btn {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.link-margin {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
/* aside navigation */
|
||||
nav.userNav {
|
||||
padding: 1em 3em 1em 1em;
|
||||
}
|
||||
nav.userNav .userNavProfile {
|
||||
text-align: center;
|
||||
}
|
||||
nav.userNav .userNavProfile::after {
|
||||
content: "";
|
||||
width: 10em;
|
||||
display: inline-block;
|
||||
height: 1px;
|
||||
background-color: rgba(0, 0, 0, 0.5);;
|
||||
}
|
||||
nav.userNav .userNavProfile img {
|
||||
width: 7em;
|
||||
height: 7em;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
nav.userNav .userNavProfile .name {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: #353535;
|
||||
margin: .5em 1em 0 1em;
|
||||
}
|
||||
nav.userNav .links {
|
||||
padding: .7em 0;
|
||||
}
|
||||
nav.userNav .links .navItem {
|
||||
display: block;
|
||||
margin: .3em .5em;
|
||||
font-size: 1em;
|
||||
color: #707070;
|
||||
text-decoration: none;
|
||||
}
|
||||
nav.userNav .links .navItem:hover {
|
||||
color: #505050;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
nav.userNav .links .navItem.active {
|
||||
color: #673ab7;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* email popup */
|
||||
.email_popup {
|
||||
margin: 10px 4em;
|
||||
margin-top: 55px;
|
||||
background-color: white;
|
||||
border: 1px solid #d1d1d1;
|
||||
padding: 1em 2em;
|
||||
transition: transform .2s ease-in, max-height .2s, padding .5s;
|
||||
overflow: hidden;
|
||||
max-height: 100vh;
|
||||
}
|
||||
.email_popup.closed {
|
||||
transform: translateY(-15rem);
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.email_popup p {
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.email_popup span {
|
||||
float: right;
|
||||
}
|
||||
.email_popup button {
|
||||
margin: .4em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 950px) {
|
||||
body .contentWrapper.userPage {
|
||||
flex-direction: column;
|
||||
}
|
||||
nav.userNav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
.email_popup {
|
||||
margin: 10px;
|
||||
margin-top: 55px;
|
||||
}
|
||||
.contentCard {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
nav.userNav .links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
}
|
||||
nav.userNav .links .navItem {
|
||||
display: inline-block;
|
||||
}
|
||||
nav.userNav .userNavProfile::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.contentCard {
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
nav.userNav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
nav.userNav .links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
nav.userNav .userNavProfile::after {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
section.bigCardWrapper.double {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-width: 80vw;
|
||||
margin: auto;
|
||||
}
|
||||
section.bigCardWrapper.double > .bigCard {
|
||||
flex: 1;
|
||||
padding: 1em 6em;
|
||||
margin: 0;
|
||||
}
|
||||
section.bigCardWrapper.double > .bigCard:first-child {
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.listItem h2 {
|
||||
font-size: 1rem;
|
||||
color: #353535;
|
||||
margin: 1em 0 0 0;
|
||||
}
|
||||
.listItem p {
|
||||
margin: .2em 0 0 0;
|
||||
}
|
||||
|
||||
.listItem.state1::before, .listItem.state2::before, .listItem.state3::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: .2em;
|
||||
left: -1.5em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
background-color: #f57c7c; /* state 1: no support */
|
||||
}
|
||||
.listItem.state2::before {
|
||||
background-color: #e6ae65; /* state 2: partial support/bugged */
|
||||
}
|
||||
.listItem.state3::before {
|
||||
background-color: #58ef81; /* state 3: full support/working */
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
section.bigCardWrapper.double {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
section.bigCardWrapper.double > .bigCard {
|
||||
flex: 0;
|
||||
}
|
||||
section.bigCardWrapper.double > .bigCard:first-child {
|
||||
margin-right: 0;
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
section.bigCardWrapper.double {
|
||||
max-width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
section.bigCardWrapper.double > .bigCard {
|
||||
padding: 50px;
|
||||
}
|
||||
}
|
||||
150
assets/js/api.js
150
assets/js/api.js
@@ -1,150 +0,0 @@
|
||||
function postAjax(url, data, callbacks={}) {
|
||||
const params = (typeof data == 'string') ? data : Object.keys(data).map(function (k) {
|
||||
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
|
||||
}).join('&');
|
||||
|
||||
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
|
||||
for (const event in callbacks) {
|
||||
const handler = callbacks[event];
|
||||
|
||||
if (handler instanceof Function) {
|
||||
xhr.addEventListener(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.open('POST', url);
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.send(data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function showErrorPopup(errorText) {
|
||||
console.log(errorText);
|
||||
document.getElementById('errorBox').innerHTML = errorText;
|
||||
document.getElementById('errorBox').classList.remove('hide');
|
||||
}
|
||||
|
||||
function showSuccessPopup(successText) {
|
||||
document.getElementById('successBox').innerHTML = successText;
|
||||
document.getElementById('successBox').classList.remove('hide');
|
||||
}
|
||||
|
||||
function login() {
|
||||
var xhr = postAjax('/api/v1/login', {
|
||||
email: document.getElementById('email_input').value,
|
||||
password: document.getElementById('password_input').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
location.pathname = '/pnid/dashboard';
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function editInfo() {
|
||||
var xhr = postAjax('/api/v1/editaccount', {
|
||||
email: document.getElementById('email_input').value,
|
||||
username: document.getElementById('username_input').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
location.pathname = '/pnid/dashboard';
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function passwordForgot() {
|
||||
var xhr = postAjax('/api/v1/passwordforgot', {
|
||||
email: document.getElementById('email_input').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
document.getElementById('email_input').value = '';
|
||||
return showSuccessPopup('An email has been sent to your email address.');
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function passwordReset() {
|
||||
var xhr = postAjax('/api/v1/passwordreset', {
|
||||
password: document.getElementById('password_input').value,
|
||||
password_confirm: document.getElementById('password_confirm').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
location.pathname = '/pnid/login';
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function register() {
|
||||
var xhr = postAjax('/api/v1/register', {
|
||||
email: document.getElementById('email_input').value,
|
||||
username: document.getElementById('username_input').value,
|
||||
password: document.getElementById('password_input').value,
|
||||
confirm_password: document.getElementById('password_confirm_input').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
login();
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
var xhr = postAjax('/api/v1/sendmessage', {
|
||||
email: document.getElementById('email_input').value,
|
||||
subject: document.getElementById('subject_input').value,
|
||||
message: document.getElementById('message_input').value
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.responseText.charAt(0) !== "{") {
|
||||
return showErrorPopup('Whoops, something went wrong. Try again later.');
|
||||
}
|
||||
response = JSON.parse(xhr.responseText);
|
||||
if (response.success === true) {
|
||||
document.getElementById('email_input').value = '';
|
||||
document.getElementById('subject_input').value = '';
|
||||
document.getElementById('message_input').value = '';
|
||||
return showSuccessPopup('Your message has been sent.');
|
||||
} else if (response.errors) {
|
||||
showErrorPopup(response.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function email_resend() {
|
||||
var xhr = postAjax('/api/v1/resendemail', {});
|
||||
document.getElementById('email_verification_popup').classList.add('closed');
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
function toggleEditInfo() {
|
||||
document.getElementById('userInfo').classList.toggle('hide');
|
||||
document.getElementById('editInfo').classList.toggle('hide');
|
||||
}
|
||||
@@ -1,27 +1,5 @@
|
||||
{
|
||||
"http": {
|
||||
"port": 8080
|
||||
},
|
||||
"database": {
|
||||
"url": "mongodb://localhost:27017/pretendo_website"
|
||||
},
|
||||
"contactWebhook": {
|
||||
"port": 443,
|
||||
"host": "host.com",
|
||||
"path": "/webhookurl"
|
||||
},
|
||||
"secrets": {
|
||||
"session": "session secret here"
|
||||
},
|
||||
"email": {
|
||||
"service": "gmail",
|
||||
"auth": {
|
||||
"user": "example@email.com",
|
||||
"pass": "password"
|
||||
}
|
||||
},
|
||||
"recaptcha": {
|
||||
"siteKey": "abcd",
|
||||
"secretKey": "1234"
|
||||
}
|
||||
"http": {
|
||||
"port": 8080
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
|
||||
api.js -
|
||||
common api returns
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send generic API response
|
||||
* @param {ServerResponse} response An express ServerResponse response
|
||||
* @param {Object} data Response data
|
||||
* @param {Array} errors Request errors
|
||||
*/
|
||||
function sendReturn(response, data, errors) {
|
||||
return response.status(data.code || 200).json(
|
||||
Object.assign({
|
||||
code: 200,
|
||||
success: true,
|
||||
errors: [] + (errors ? errors : [])
|
||||
}, data)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send API 404
|
||||
* @param {ServerResponse} response An express ServerResponse response
|
||||
*/
|
||||
function sendApi404(response) {
|
||||
return sendApiError(response, 404, [
|
||||
'Endpoint not in use'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send user not authenticated error
|
||||
* @param {ServerResponse} response An express ServerResponse response
|
||||
*/
|
||||
function sendApiAuthError(response) {
|
||||
return sendApiError(response, 401, [
|
||||
'Not authenticated'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a generic API error
|
||||
* @param {ServerResponse} response An express ServerResponse response
|
||||
*/
|
||||
function sendApiGenericError(response) {
|
||||
return sendApiError(response, 400, [
|
||||
'Bad request'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an API error
|
||||
* @param {ServerResponse} response An express ServerResponse response
|
||||
*/
|
||||
function sendApiError(response, code, errors) {
|
||||
return sendReturn(response, {
|
||||
code,
|
||||
success: false
|
||||
}, errors);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendReturn,
|
||||
sendApi404,
|
||||
sendApiGenericError,
|
||||
sendApiError,
|
||||
sendApiAuthError
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const config = require('../config');
|
||||
|
||||
const transporter = nodemailer.createTransport(config.email);
|
||||
|
||||
/**
|
||||
* Sends an email with the specified subject and message to an email address
|
||||
* @param {String} email the destination email address
|
||||
* @param {String} subject The Subject of the email
|
||||
* @param {String} message The body of the email
|
||||
*/
|
||||
function send(email, subject = 'No email subject provided', message = 'No email body provided') {
|
||||
const options = {
|
||||
from: config.email.address,
|
||||
to: email,
|
||||
subject: subject,
|
||||
html: message
|
||||
};
|
||||
|
||||
transporter.sendMail(options, (error, info) => {
|
||||
if (error) {
|
||||
console.warn(error);
|
||||
} else {
|
||||
console.log(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
send: send
|
||||
};
|
||||
@@ -6,38 +6,7 @@ small commonly used utilities
|
||||
*/
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const logger = require('winston');
|
||||
|
||||
// shows 404 template. takes express response object
|
||||
function send404(res) {
|
||||
res.status(404).render('404');
|
||||
}
|
||||
|
||||
function templateReadyUser(req) {
|
||||
// normal user logged in
|
||||
const isLoggedIn = req.user ? req.user.role != 'admin' : false;
|
||||
const user = req.user ? req.user.toObject() : undefined;
|
||||
return {
|
||||
isLoggedIn,
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a list of possible locales:
|
||||
/*
|
||||
* [
|
||||
* {
|
||||
* region: 'US',
|
||||
* language: 'en',
|
||||
* display: 'American English',
|
||||
* flag_id: 1 // I dunno maybe we display a local list that also displays flags?
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
function getLocales() {
|
||||
return [];
|
||||
}
|
||||
const logger = require('../logger');
|
||||
|
||||
// Returns a locale
|
||||
function getLocale(region, language) {
|
||||
@@ -47,7 +16,7 @@ function getLocale(region, language) {
|
||||
return require(path);
|
||||
}
|
||||
|
||||
logger.log('warn', `Could not find locale ${region}_${language}! Loading default`);
|
||||
logger.warn(`Could not find locale ${region}_${language}! Loading default`);
|
||||
|
||||
return getDefaultLocale();
|
||||
}
|
||||
@@ -57,15 +26,7 @@ function getDefaultLocale(locale='default') {
|
||||
return require(`${__dirname}/../locales/${locale}.json`);
|
||||
}
|
||||
|
||||
function generateRandomInt(length = 4) {
|
||||
return Math.floor(Math.pow(10, length-1) + Math.random() * 9 * Math.pow(10, length-1));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
send404,
|
||||
templateReadyUser,
|
||||
getLocales,
|
||||
getLocale,
|
||||
getDefaultLocale,
|
||||
generateRandomInt
|
||||
getDefaultLocale
|
||||
};
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"brand": "Pretendo",
|
||||
"links": {
|
||||
"news": "News",
|
||||
"progress": "Progress",
|
||||
"contact": "Contact",
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"logout": "Logout"
|
||||
}
|
||||
"brand": "Pretendo"
|
||||
},
|
||||
"home": {
|
||||
"header": {
|
||||
@@ -16,117 +8,16 @@
|
||||
"text": "An open source Nintendo Network replacement service"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"summary": {
|
||||
"caption": "All in one",
|
||||
"title": "Progress Summary",
|
||||
"text": "We should probably pull this from a database ngl"
|
||||
},
|
||||
"compatibility": {
|
||||
"caption": "Useful info",
|
||||
"title": "Game Compatibility"
|
||||
},
|
||||
"misc": {
|
||||
"caption": "Less useful info",
|
||||
"title": "Other"
|
||||
}
|
||||
},
|
||||
"news": {
|
||||
"caption": "Read up",
|
||||
"title": "All news",
|
||||
"button_text": "Read more"
|
||||
},
|
||||
"contact": {
|
||||
"caption": "Have a question?",
|
||||
"title": "Contact Us",
|
||||
"button_text": "Send Message",
|
||||
"email_placeholder": "email@provider.com",
|
||||
"subject_placeholder": "Subject",
|
||||
"message_placeholder": "Message"
|
||||
},
|
||||
"login": {
|
||||
"card": {
|
||||
"caption": "Sign in",
|
||||
"title": "Login",
|
||||
"email_text": "Username or Email",
|
||||
"password_text": "Password",
|
||||
"button_text": "Sign In",
|
||||
"forgotpass_introduction": "Forgot password?",
|
||||
"forgotpass_text": "Click here to reset it.",
|
||||
"link_introduction": "Don't have an account yet?",
|
||||
"link_text": "Click here to register."
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"card": {
|
||||
"title": "Register",
|
||||
"birthdate_text": "Birthday",
|
||||
"gender_text": "Gender",
|
||||
"email_text": "Email Address",
|
||||
"username_text": "Username",
|
||||
"password_text": "Password",
|
||||
"password_confirm_text": "Confirm Password",
|
||||
"caption": "Sign Up",
|
||||
"button_text": "Sign up",
|
||||
"link_introduction": "Already have an account?",
|
||||
"link_text": "Click here to login."
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"card": {
|
||||
"caption": "Forgot password?",
|
||||
"title": "Reset password",
|
||||
"password_text": "New password",
|
||||
"password_confirm_text": "Confirm password",
|
||||
"button_text": "Reset password"
|
||||
}
|
||||
},
|
||||
"forgot": {
|
||||
"card": {
|
||||
"caption": "Did you forget something?",
|
||||
"title": "Forgot password",
|
||||
"email_text": "Email address",
|
||||
"button_text": "Send email"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"caption": "What is this?",
|
||||
"title": "About",
|
||||
"text": "Pretendo is an open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS/2DS family of consoles. Our goal is to preserve the online functionality of these consoles, to allow players to continue to play their favorite WiiU and 3DS/2DS games to their fullest capacity."
|
||||
"text": "Pretendo is an open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles. Our goal is to preserve the online functionality of these consoles, to allow players to continue to play their favorite WiiU and 3DS games to their fullest capacity."
|
||||
},
|
||||
"credits": {
|
||||
"caption": "Who are responsible?",
|
||||
"caption": "The people responsible",
|
||||
"title": "Credits"
|
||||
},
|
||||
"latest_news": {
|
||||
"caption": "Catch up",
|
||||
"title": "Latest news",
|
||||
"description": "Pretendo is constantly evolving to create the best possible servers. We release posts to showcase what we have been working on and how far we got with certain things.",
|
||||
"button_text": "All posts",
|
||||
"news_button_text": "Read more"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_login": "Invalid username/password combination",
|
||||
"account_exists": "An account with that username already exists!"
|
||||
},
|
||||
"verify_email_popup": {
|
||||
"text": "Your email has not been verified yet. You are restricted from certain features.",
|
||||
"change_button_text": "Change email",
|
||||
"repeat_button_text": "Send again"
|
||||
},
|
||||
"dashboard": {
|
||||
"navigation": {
|
||||
"links": {
|
||||
"account": "Account",
|
||||
"soon": "Coming soon"
|
||||
}
|
||||
},
|
||||
"account": {
|
||||
"caption": "Pretendo Network ID",
|
||||
"title": "Account information"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"text": "Copyright 2019 PretendoNetwork - Site designed and developed by MrJVS, ben and Jip"
|
||||
"text": "Copyright 2020 PretendoNetwork - Site designed and developed by mrjvs, ben and jip"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"brand": "ERROR",
|
||||
"links": {
|
||||
"news": "ERROR",
|
||||
"progress": "ERROR",
|
||||
"contact": "ERROR",
|
||||
"login": "ERROR",
|
||||
"register": "ERROR",
|
||||
"logout": "ERROR"
|
||||
}
|
||||
"brand": "ERROR"
|
||||
},
|
||||
"home": {
|
||||
"header": {
|
||||
@@ -16,60 +8,15 @@
|
||||
"text": "ERROR"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"summary": {
|
||||
"title": "ERROR",
|
||||
"text": "ERROR"
|
||||
},
|
||||
"compatability": {
|
||||
"title": "ERROR"
|
||||
},
|
||||
"misc": {
|
||||
"title": "ERROR"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "ERROR",
|
||||
"button_text": "ERROR",
|
||||
"email_placeholder": "ERROR",
|
||||
"subject_placeholder": "ERROR",
|
||||
"message_placeholder": "ERROR"
|
||||
},
|
||||
"login": {
|
||||
"card": {
|
||||
"title": "ERROR",
|
||||
"email_text": "ERROR",
|
||||
"password_text": "ERROR",
|
||||
"email_placeholder": "ERROR",
|
||||
"password_placeholder": "ERROR",
|
||||
"button_text": "Sign In"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"card": {
|
||||
"title": "ERROR",
|
||||
"birthdate_text": "ERROR",
|
||||
"gender_text": "ERROR",
|
||||
"email_text": "ERROR",
|
||||
"username_text": "ERROR",
|
||||
"password_text": "ERROR",
|
||||
"password_confirm_text": "ERROR",
|
||||
"email_placeholder": "ERROR",
|
||||
"password_placeholder": "ERROR",
|
||||
"caption": "ERROR"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"caption": "ERROR",
|
||||
"title": "ERROR",
|
||||
"text": "ERROR"
|
||||
},
|
||||
"credits": {
|
||||
"caption": "ERROR",
|
||||
"title": "ERROR"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_login": "ERROR",
|
||||
"account_exists": "ERROR"
|
||||
},
|
||||
"footer": {
|
||||
"text": "ERROR"
|
||||
}
|
||||
|
||||
89
logger.js
89
logger.js
@@ -1,41 +1,54 @@
|
||||
const winston = require('winston');
|
||||
const fs = require('fs-extra');
|
||||
require('colors');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'verbose',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.printf(log => {
|
||||
return `${log.timestamp} | ${log.level}: ${log.message}`;
|
||||
})
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console({ colorize: true }),
|
||||
new winston.transports.File({
|
||||
colorize: false,
|
||||
json: false,
|
||||
filename: `${__dirname}/logs/events.log`
|
||||
}),
|
||||
new winston.transports.File({
|
||||
colorize: false,
|
||||
json: false,
|
||||
filename: `${__dirname}/logs/error.log`,
|
||||
level: 'error'
|
||||
}),
|
||||
new winston.transports.File({
|
||||
colorize: false,
|
||||
json: false,
|
||||
filename: `${__dirname}/logs/warn.log`,
|
||||
level: 'warn'
|
||||
}),
|
||||
new winston.transports.File({
|
||||
colorize: false,
|
||||
json: false,
|
||||
filename: `${__dirname}/logs/debug.log`,
|
||||
level: 'debug'
|
||||
})
|
||||
]
|
||||
});
|
||||
class Logger {
|
||||
constructor(root = '') {
|
||||
fs.ensureDirSync(`${root}/logs`);
|
||||
|
||||
winston.add(logger);
|
||||
this.root = root;
|
||||
this.streams = {
|
||||
latest: fs.createWriteStream(`${this.root}/logs/latest.log`),
|
||||
success: fs.createWriteStream(`${this.root}/logs/success.log`),
|
||||
error: fs.createWriteStream(`${this.root}/logs/error.log`),
|
||||
warn: fs.createWriteStream(`${this.root}/logs/warn.log`),
|
||||
info: fs.createWriteStream(`${this.root}/logs/info.log`)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
module.exports = winston;
|
||||
success(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
|
||||
this.streams.success.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.green.bold);
|
||||
}
|
||||
|
||||
error(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
|
||||
this.streams.error.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.red.bold);
|
||||
}
|
||||
|
||||
warn(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
|
||||
this.streams.warn.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.yellow.bold);
|
||||
}
|
||||
|
||||
info(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
|
||||
this.streams.info.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.cyan.bold);
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger(__dirname);
|
||||
|
||||
module.exports = logger;
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
|
||||
admin-authentication.js -
|
||||
Middleware file for authentication checking
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const apiHelper = require('../helpers/api');
|
||||
|
||||
// middleware to use if admin authentication is required
|
||||
function adminAuthNeeded(req, res, next) {
|
||||
if (req.isAuthenticated() && req.user.role && req.user.role === 'admin') {
|
||||
return next();
|
||||
} else {
|
||||
apiHelper.sendApiAuthError(res);
|
||||
}
|
||||
}
|
||||
|
||||
// middleware to use if pnid auth authentication is required
|
||||
function pnidApiAuthNeeded(req, res, next) {
|
||||
if (req.isAuthenticated() && !req.user.role) { // having no role means its a normal account. only admin accounts have roles
|
||||
return next();
|
||||
} else {
|
||||
apiHelper.sendApiAuthError(res);
|
||||
}
|
||||
}
|
||||
|
||||
// middleware to use if pnid authentication is required
|
||||
function pnidAuthNeeded(req, res, next) {
|
||||
if (req.isAuthenticated() && !req.user.role) { // having no role means its a normal account. only admin accounts have roles
|
||||
return next();
|
||||
} else {
|
||||
res.redirect('/pnid/login');
|
||||
}
|
||||
}
|
||||
|
||||
// middleware to use if authentication is optional
|
||||
function authOptional(req, res, next) {
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
adminAuthNeeded,
|
||||
pnidAuthNeeded,
|
||||
pnidApiAuthNeeded,
|
||||
authOptional
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
|
||||
admin-user.js -
|
||||
file containing the model file for admin user
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const mongoose = require('mongoose');
|
||||
const uniqueValidator = require('mongoose-unique-validator');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
// admin user database layout
|
||||
const adminUserSchema = new mongoose.Schema({
|
||||
username: {
|
||||
type: String,
|
||||
required: [true, 'Username is required.'],
|
||||
minlength: [1, 'Username must be at-least 1 character.'],
|
||||
maxlength: [42, 'Username cannot be more than 42 characters long.'],
|
||||
unique: true,
|
||||
trim: true
|
||||
},
|
||||
// non hashed, gets hashed at save
|
||||
password: {
|
||||
type: String,
|
||||
required: [true, 'Password is required.'],
|
||||
minlength: [7, 'Password must be at-least 7 characters.'],
|
||||
maxlength: [256, 'Password cannot be more than 256 characters long.'],
|
||||
trim: true
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'admin'
|
||||
}
|
||||
});
|
||||
|
||||
adminUserSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'});
|
||||
|
||||
// hashing password
|
||||
adminUserSchema.pre('save', function(next) {
|
||||
// only if modified
|
||||
if (!this.isModified('password')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// hashing
|
||||
bcrypt.hash(this.get('password'), 10, (err, hash) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
this.set('password', hash);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
adminUserSchema.statics.findByUsername = function(username) {
|
||||
return this.model('adminUser').findOne({
|
||||
username
|
||||
});
|
||||
};
|
||||
|
||||
const adminUserModel = mongoose.model('adminUser', adminUserSchema);
|
||||
|
||||
module.exports = {
|
||||
adminUserModel,
|
||||
adminUserSchema
|
||||
};
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
|
||||
blog-post.js -
|
||||
file containing the model file for a blog post
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const mongoose = require('mongoose');
|
||||
const postAuthor = require('./post-author').postAuthorModel;
|
||||
const showdown = require('showdown');
|
||||
const moment = require('moment');
|
||||
const converter = new showdown.Converter();
|
||||
converter.setFlavor('github');
|
||||
|
||||
// admin user database layout
|
||||
const blogPostSchema = new mongoose.Schema({
|
||||
// html in content
|
||||
content: {
|
||||
type: String,
|
||||
required: [true, 'Content is required.'],
|
||||
trim: true
|
||||
},
|
||||
// title of blog post
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, 'Name is required']
|
||||
},
|
||||
// short description of blog post
|
||||
short: {
|
||||
type: String,
|
||||
required: [true, 'Short version is required']
|
||||
},
|
||||
meta: {
|
||||
slug: {
|
||||
type: String,
|
||||
required: [true, 'Author is required'],
|
||||
trim: true
|
||||
},
|
||||
author: {
|
||||
type: String,
|
||||
required: [true, 'Author is required'],
|
||||
trim: true
|
||||
},
|
||||
date: {
|
||||
type: Date,
|
||||
default: () => {
|
||||
return moment(moment().format('YYYY-MM-DD'));
|
||||
}
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: [true, 'category is required'],
|
||||
trim: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
blogPostSchema.methods.postTemplate = function(callback) {
|
||||
const self = this;
|
||||
postAuthor.findById(this.meta.author, function (err, author) {
|
||||
callback(err, {
|
||||
content: self.content,
|
||||
title: self.name,
|
||||
date: self.meta.date,
|
||||
dateString: moment(self.meta.date).format('YYYY-MM-DD'),
|
||||
category: self.meta.category,
|
||||
author: author.authorTemplate()
|
||||
});
|
||||
});
|
||||
};
|
||||
blogPostSchema.methods.postShortTemplate = function() {
|
||||
return {
|
||||
content: this.short,
|
||||
title: this.name,
|
||||
url: moment(this.meta.date).format('YYYY-MM-DD') + '/' + this.meta.slug
|
||||
};
|
||||
};
|
||||
|
||||
blogPostSchema.statics.markdownToHtml = function(markdown) {
|
||||
return converter.makeHtml(markdown);
|
||||
};
|
||||
blogPostSchema.statics.getPost = function(date, slug, callback) {
|
||||
return blogPostModel.findOne({
|
||||
'meta.date': date,
|
||||
'meta.slug': slug
|
||||
}, callback);
|
||||
};
|
||||
|
||||
blogPostSchema.statics.latestPostsShortTemlate = function(amount, callback) {
|
||||
blogPostModel.find({}).sort({'meta.date': 'desc'}).exec(function(err, posts) {
|
||||
if (err) return callback(err);
|
||||
const out = [];
|
||||
for (let i = 0, l = posts.length; i < ( amount+1 < l ? amount : l); i++)
|
||||
out.push(posts[i].postShortTemplate());
|
||||
callback(err, out);
|
||||
});
|
||||
};
|
||||
|
||||
const blogPostModel = mongoose.model('blogPost', blogPostSchema);
|
||||
|
||||
module.exports = {
|
||||
blogPostModel,
|
||||
blogPostSchema
|
||||
};
|
||||
184
models/pnid.js
184
models/pnid.js
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
|
||||
pnid.js -
|
||||
file containing the model for pretendo network id's
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const mongoose = require('mongoose');
|
||||
const uniqueValidator = require('mongoose-unique-validator');
|
||||
const randtoken = require('rand-token');
|
||||
const bcrypt = require('bcrypt');
|
||||
const utilHelper = require('../helpers/util');
|
||||
|
||||
// admin user database layout
|
||||
const PNIDSchema = new mongoose.Schema({
|
||||
email: {
|
||||
type: String,
|
||||
required: [true, 'Email is required.'],
|
||||
unique: true,
|
||||
trim: true,
|
||||
uppercase: true,
|
||||
validate: [validateEmail, 'Please fill a valid email address']
|
||||
},
|
||||
email_validated: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
email_validation_code: {
|
||||
type: Number,
|
||||
required: [true, 'Email validation code is required.'],
|
||||
},
|
||||
email_validation_token: {
|
||||
type: String,
|
||||
required: [true, 'Email validation token is required.'],
|
||||
},
|
||||
// non hashed, gets hashed at save
|
||||
password: {
|
||||
type: String,
|
||||
required: [true, 'Password is required.'],
|
||||
minlength: [7, 'Password must be at-least 7 characters.'],
|
||||
maxlength: [256, 'Password cannot be more than 256 characters long.'],
|
||||
trim: true
|
||||
},
|
||||
pnid: {
|
||||
pid: {
|
||||
type: Number,
|
||||
unique: true
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
unique: true,
|
||||
validate: [validateUsername, 'Please fill a valid username'],
|
||||
required: true
|
||||
},
|
||||
username_lower: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
avatar_url: {
|
||||
type: String,
|
||||
default: '/assets/images/default_avatar.png'
|
||||
}
|
||||
},
|
||||
consoles: []
|
||||
});
|
||||
|
||||
function validateEmail(email) {
|
||||
// eslint throws "unnecesary character escape"
|
||||
const re = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/; // eslint-disable-line
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
function validateUsername(username) {
|
||||
// eslint throws "unnecesary character escape"
|
||||
const re = /^[a-zA-Z0-9]+([_ -]?[a-zA-Z0-9])*$/; // eslint-disable-line
|
||||
return re.test(username);
|
||||
}
|
||||
|
||||
PNIDSchema.plugin(uniqueValidator, {message: '{PATH} already in use.'});
|
||||
|
||||
// hashing password
|
||||
PNIDSchema.pre('save', function(next) {
|
||||
// only if modified
|
||||
if (!this.isModified('password')) {
|
||||
return next();
|
||||
}
|
||||
// hashing
|
||||
const primaryhash = PNIDModel.hashPasswordPrimary(this.get('password'), this.get('pnid.pid'));
|
||||
bcrypt.hash(primaryhash, 10, (err, hash) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
this.set('password', hash);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
PNIDSchema.statics.findByEmail = function(email) {
|
||||
return this.model('pnid').findOne({
|
||||
email: email.toUpperCase()
|
||||
});
|
||||
};
|
||||
|
||||
PNIDSchema.statics.findUser = async function(key) {
|
||||
const user = await this.model('pnid').findOne({
|
||||
email: key.toUpperCase()
|
||||
});
|
||||
if (!user) {
|
||||
return await this.model('pnid').findOne({
|
||||
'pnid.username_lower': key.toLowerCase()
|
||||
});
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
PNIDSchema.statics.hashPasswordPrimary = function(password, pid) {
|
||||
const buff1 = require('python-struct').pack('<I', pid);
|
||||
const buff2 = Buffer.from(password).toString('ascii');
|
||||
|
||||
const unpacked = Buffer.from(bufferToHex(buff1) + '\x02eCF' + buff2, 'ascii');
|
||||
const hashed = require('crypto').createHash('sha256').update(unpacked).digest().toString('hex');
|
||||
|
||||
return hashed;
|
||||
};
|
||||
|
||||
function bufferToHex(buff) {
|
||||
let result = '';
|
||||
const arr = buff.toString('hex').match(/.{1,2}/g);
|
||||
for (let i=0;i<arr.length;i++) {
|
||||
const char = arr[i];
|
||||
result += String.fromCharCode(parseInt(char, 16));
|
||||
}
|
||||
result.replace(/\\/g, '\');
|
||||
return result;
|
||||
}
|
||||
|
||||
PNIDSchema.statics.generatePID = async function() {
|
||||
// Quick, dirty fix for PIDs
|
||||
const pid = Math.floor(Math.random() * (4294967295 - 1000000000) + 1000000000);
|
||||
const pid_inuse = await PNIDModel.findOne({
|
||||
'pnid.pid': pid
|
||||
});
|
||||
|
||||
if (pid_inuse) {
|
||||
return await PNIDModel.generatePID();
|
||||
}
|
||||
|
||||
return pid;
|
||||
};
|
||||
|
||||
PNIDSchema.statics.generateEmailValidationCode = async function() {
|
||||
const code = utilHelper.generateRandomInt(6);
|
||||
const code_inuse = await PNIDModel.findOne({
|
||||
'email_validation_code': code
|
||||
});
|
||||
|
||||
if (code_inuse) {
|
||||
return await PNIDModel.generateEmailValidationCode();
|
||||
}
|
||||
|
||||
return code;
|
||||
};
|
||||
|
||||
PNIDSchema.statics.generateEmailValidationToken = async function() {
|
||||
const token = randtoken.generate(32);
|
||||
const token_inuse = await PNIDModel.findOne({
|
||||
'email_validation_token': token
|
||||
});
|
||||
|
||||
if (token_inuse) {
|
||||
return await PNIDModel.generateEmailValidationToken();
|
||||
}
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
const PNIDModel = mongoose.model('pnid', PNIDSchema);
|
||||
|
||||
module.exports = {
|
||||
PNIDModel,
|
||||
PNIDSchema
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
|
||||
post-author.js -
|
||||
file containing the model for authors
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
// post author database layout
|
||||
const postAuthorSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, 'Name is required'],
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, 'Description is required'],
|
||||
trim: true
|
||||
},
|
||||
// profile picture image link
|
||||
image: {
|
||||
type: String,
|
||||
trim: true
|
||||
}
|
||||
});
|
||||
|
||||
postAuthorSchema.methods.authorTemplate = function() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
profilePicture: this.image
|
||||
};
|
||||
};
|
||||
|
||||
const postAuthorModel = mongoose.model('postAuthor', postAuthorSchema);
|
||||
|
||||
module.exports = {
|
||||
postAuthorModel,
|
||||
postAuthorSchema
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
|
||||
progress-list.js -
|
||||
file containing the model for progress list
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
// progress text schema
|
||||
const progressListSchema = new mongoose.Schema({
|
||||
state: {
|
||||
type: Number, /* 0 - nothing, 1 - no support, 2 - partially working, 3 - works */
|
||||
default: 0
|
||||
},
|
||||
isGame: {
|
||||
type: Boolean, /* true - is game, false - is backend service */
|
||||
required: [true, 'isGame is required']
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: [true, 'Title is required']
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, 'Description is required']
|
||||
}
|
||||
});
|
||||
|
||||
const progressListModel = mongoose.model('progress', progressListSchema);
|
||||
|
||||
module.exports = {
|
||||
progressListModel,
|
||||
progressListSchema
|
||||
};
|
||||
2583
package-lock.json
generated
2583
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -15,25 +15,12 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcrypt": "^3.0.1",
|
||||
"colors": "^1.1.2",
|
||||
"connect-mongo": "^2.0.1",
|
||||
"express": "^4.16.2",
|
||||
"express-handlebars": "^3.0.0",
|
||||
"express-recaptcha": "^4.0.3",
|
||||
"express-session": "^1.15.6",
|
||||
"fs-extra": "^7.0.1",
|
||||
"moment": "^2.22.2",
|
||||
"mongoose": "^5.3.2",
|
||||
"mongoose-unique-validator": "^2.0.2",
|
||||
"morgan": "^1.9.1",
|
||||
"multer": "^1.4.1",
|
||||
"nodemailer": "^4.7.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"python-struct": "^1.0.6",
|
||||
"rand-token": "^0.4.0",
|
||||
"showdown": "^1.8.6",
|
||||
"showdown": "^1.9.1",
|
||||
"winston": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
|
||||
passport.config.js -
|
||||
Passport setup file. handles authentication strategy and session setup
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
const passport = require('passport');
|
||||
const bcrypt = require('bcrypt');
|
||||
const adminUserModel = require('./models/admin-user').adminUserModel;
|
||||
const PNIDModel = require('./models/pnid').PNIDModel;
|
||||
|
||||
// setup authentication
|
||||
module.exports = (app) => {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
passport.use('adminUserStrategy', new LocalStrategy(
|
||||
|
||||
(username, password, done) => {
|
||||
// find user in database
|
||||
adminUserModel.findByUsername(username).then((user) => {
|
||||
if (!user) {
|
||||
// user doesnt exist
|
||||
return done(null, false, {message: 'Incorrect user'});
|
||||
}
|
||||
|
||||
bcrypt.compare(password, user.password, (err, res) => {
|
||||
if (err || !res) {
|
||||
// error comparing hashes
|
||||
return done(null, false, {message: 'Incorrect password'});
|
||||
}
|
||||
|
||||
// password is correct, return user
|
||||
return done(null, user);
|
||||
|
||||
});
|
||||
}).catch((err) => {
|
||||
if (err) {
|
||||
// error finding in database
|
||||
return done(null, false, {code: 500});
|
||||
}
|
||||
});
|
||||
}
|
||||
));
|
||||
passport.use('PNIDStrategy', new LocalStrategy({
|
||||
usernameField: 'email'
|
||||
}, (email, password, done) => {
|
||||
// find user in database, email = username or email
|
||||
PNIDModel.findUser(email).then((user) => {
|
||||
if (!user) {
|
||||
// user doesnt exist
|
||||
return done(null, false, {message: 'Incorrect email'});
|
||||
}
|
||||
bcrypt.compare(password, user.password, (err, res) => {
|
||||
if (err || !res) {
|
||||
// error comparing hashes
|
||||
// try primary hash on password and checking again.
|
||||
bcrypt.compare(PNIDModel.hashPasswordPrimary(password, user.pnid.pid), user.password, (err, res) => {
|
||||
if (err || !res) {
|
||||
// error comparing hashes
|
||||
// password hashed and non hashed both incorrect.
|
||||
return done(null, false, {message: 'Incorrect password'});
|
||||
}
|
||||
// password is correct, return user
|
||||
return done(null, user);
|
||||
});
|
||||
} else {
|
||||
// password is correct, return user
|
||||
return done(null, user);
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
if (err) {
|
||||
// error finding in database
|
||||
return done(null, false, {code: 500});
|
||||
}
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
//Configuring app to have sessions, dont change since it would break everything
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
//SERIOUSLY. DONT TOUCH THIS SPAGHETTI, IT TOOK ME FOREVER TO GET THIS TO WORK!!!!1!!
|
||||
passport.deserializeUser(function(id, done) {
|
||||
adminUserModel.findById(id, function(err, user) {
|
||||
if (err || !user) {
|
||||
PNIDModel.findById(id, function(err, user) {
|
||||
done(err, user);
|
||||
});
|
||||
} else {
|
||||
done(err, user);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
User-agent: *
|
||||
Disallow: /api/
|
||||
Disallow: /admin
|
||||
Disallow: /admin/
|
||||
Disallow: /pnid/dashboard
|
||||
Disallow: /pnid/dashboard/
|
||||
494
routes/admin.js
494
routes/admin.js
@@ -1,494 +0,0 @@
|
||||
/*
|
||||
|
||||
admin.js -
|
||||
file for handling admin api.
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const router = require('express').Router();
|
||||
const passport = require('passport');
|
||||
const moment = require('moment');
|
||||
const apiHelper = require('../helpers/api');
|
||||
const userMiddleware = require('../middleware/authentication');
|
||||
|
||||
// database models
|
||||
const adminUser = require('../models/admin-user');
|
||||
const blogPost = require('../models/blog-post');
|
||||
const postAuthor = require('../models/post-author');
|
||||
const progressList = require('../models/progress-list');
|
||||
|
||||
// renders admin.hbs
|
||||
router.get('/admin', (request, response) => {
|
||||
response.render('admin', {
|
||||
title: 'Pretendo | Admin',
|
||||
description: '',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/login
|
||||
*
|
||||
* signs admin user in
|
||||
*
|
||||
* post {
|
||||
* username
|
||||
* password
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean
|
||||
* username: undefined | string - only if login was successfull
|
||||
* role: undefined | string - role of user if login was successfull
|
||||
* errors: Strings[messages] - not yet :(
|
||||
* }
|
||||
*/
|
||||
// TODO make login somehow display errors in correct format.
|
||||
// middleware does the authentication work. this just returns a success
|
||||
router.post('/admin/api/v1/login', passport.authenticate('adminUserStrategy'), (request, response) => {
|
||||
const user = request.user;
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
username: user.username,
|
||||
role: user.role ? user.role : undefined
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/register
|
||||
* - requires admin auth
|
||||
*
|
||||
* registers a new admin user
|
||||
*
|
||||
* post {
|
||||
* username - username of new admin account
|
||||
* password - password of new admin account
|
||||
* }
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if register was successull
|
||||
* username: undefined | string - username if register was successfull
|
||||
* role: undefined | string - role of user if register was successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/register', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
if (!request.body) {
|
||||
// no post body
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { username, password } = request.body;
|
||||
const newUser = new adminUser.adminUserModel({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
// saving to database
|
||||
newUser.save().then((user) => {
|
||||
return apiHelper.sendReturn(response, {
|
||||
username: user.username,
|
||||
role: user.role ? user.role : undefined
|
||||
});
|
||||
}).catch((rejection) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
return apiHelper.sendApiError(response, 500, [rejection]);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/removeadmin
|
||||
* - requires admin auth
|
||||
*
|
||||
* registers a new admin user
|
||||
*
|
||||
* post {
|
||||
* id - id of the admin user
|
||||
* }
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if delete was successull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/removeadmin', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
if (!request.body) {
|
||||
// no post body
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { id } = request.body;
|
||||
adminUser.adminUserModel.findByIdAndDelete(id, (error) => {
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
// successfull
|
||||
return apiHelper.sendReturn(response, {});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/listadmins
|
||||
* - requires admin auth
|
||||
*
|
||||
* gets list of admins
|
||||
*
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if delete was successull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/admin/api/v1/listadmins', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
adminUser.adminUserModel.find({}, (error, admins) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
// formats admin list and removes password hash
|
||||
const output = [];
|
||||
for (let i = 0, l = admins.length; i < l; i++) {
|
||||
admins[i].password = undefined;
|
||||
output.push(admins[i]);
|
||||
}
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
admins: output
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/check
|
||||
*
|
||||
* checks if admin logged in
|
||||
*
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if request was without errors
|
||||
* isAuthed: boolean - true if logged in
|
||||
* role: undefined | string - returns user role
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/admin/api/v1/check', userMiddleware.authOptional, (request, response) => {
|
||||
const user = request.user;
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
isAuthed: user ? true : false,
|
||||
role: user ? (user.role ? user.role : undefined) : undefined
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/logout
|
||||
*
|
||||
* logs out admin user
|
||||
*
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if logout is successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/admin/api/v1/logout', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
request.logout();
|
||||
return apiHelper.sendReturn(response, {});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/newpost
|
||||
*
|
||||
* posts a new blog post
|
||||
*
|
||||
* post {
|
||||
* content - content of the blog post in markdown
|
||||
* title - title of the blog post
|
||||
* author - id of the author
|
||||
* short - short description of content in plain text
|
||||
* category - category name of the blog post
|
||||
*
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if login succesfull
|
||||
* url: string | undefined - url of the blog post if successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/newpost', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { content, title, author, category, short } = request.body;
|
||||
const newBlogPost = new blogPost.blogPostModel({
|
||||
content: blogPost.blogPostModel.markdownToHtml(content),
|
||||
name: title,
|
||||
short,
|
||||
meta: {
|
||||
author,
|
||||
category,
|
||||
slug: title // convert title to slug
|
||||
.trim()
|
||||
.replace(/\s/g, '-')
|
||||
.replace(/[^A-z0-9-]/g, '')
|
||||
.toLowerCase()
|
||||
}
|
||||
});
|
||||
|
||||
// saving post to database
|
||||
newBlogPost.save().then((post) => {
|
||||
return apiHelper.sendReturn(response, {
|
||||
url: moment(post.meta.date).format('YYYY-MM-DD') + '/' + post.meta.slug
|
||||
});
|
||||
}).catch((rejection) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
return apiHelper.sendApiError(response, 500, [rejection]);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/editpost
|
||||
*
|
||||
* edits a blog post
|
||||
*
|
||||
* post {
|
||||
* id - id of the blog post to be edited
|
||||
* content - content of the blog post IN HTML
|
||||
* title - title of the blog post
|
||||
* author - id of the author
|
||||
* short - short description of content in plain text
|
||||
* category - category name of the blog post
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if login succesfull
|
||||
* url: string | undefined - url of the blog post if successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/editpost', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { id, content, title, author, category, short } = request.body;
|
||||
blogPost.blogPostModel.findByIdAndUpdate(id, {
|
||||
'content': content,
|
||||
'name': title,
|
||||
'short': short,
|
||||
'meta.author': author,
|
||||
'meta.category': category
|
||||
}, (error, post) => {
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
url: moment(post.meta.date, 'YYYY-MM-DD') + '/' + post.meta.slug
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/newauthor
|
||||
*
|
||||
* creates new author
|
||||
*
|
||||
* post {
|
||||
* name - author name
|
||||
* description - author description
|
||||
* image - image url for profile picture
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if author succesfull
|
||||
* id: string | undefined - id of the new author if successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/newauthor', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { name, description, image } = request.body;
|
||||
const newAuthor = new postAuthor.postAuthorModel({
|
||||
name,
|
||||
description,
|
||||
image
|
||||
});
|
||||
|
||||
// saving author to database
|
||||
newAuthor.save().then((author) => {
|
||||
return apiHelper.sendReturn(response, {
|
||||
id: author.id
|
||||
});
|
||||
}).catch((rejection) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
return apiHelper.sendApiError(response, 500, [rejection]);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/editauthor
|
||||
*
|
||||
* edit an existing author
|
||||
*
|
||||
* post {
|
||||
* id - id of author to edit
|
||||
* name - author name
|
||||
* description - author description
|
||||
* image - image url for profile picture
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if author succesfull
|
||||
* id: String - id of the edited author
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/editauthor', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
const { id, name, description, image } = request.body;
|
||||
|
||||
// updating author in database
|
||||
postAuthor.postAuthorModel.findByIdAndUpdate(id, {
|
||||
name,
|
||||
description,
|
||||
image
|
||||
}, (error, author) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
id: author.id
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/newprogress
|
||||
*
|
||||
* creates a new progress entry
|
||||
*
|
||||
* post {
|
||||
* title - progress entry name
|
||||
* description - progress entry description
|
||||
* state - 0: backend service entry, 1: no support, 2: partial support, 3: fullly working
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if progress succesfull
|
||||
* id: String | undefined - sends if successfull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/newprogress', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
// parses state and isGame to be valid
|
||||
const { title, description } = request.body;
|
||||
let { state } = request.body;
|
||||
let isGame = false;
|
||||
if (state != '1' && state != '2' && state != '3') {
|
||||
state = undefined;
|
||||
} else {
|
||||
state = parseInt(state);
|
||||
isGame = true;
|
||||
}
|
||||
|
||||
const newProgress = new progressList.progressListModel({
|
||||
title,
|
||||
description,
|
||||
isGame,
|
||||
state
|
||||
});
|
||||
|
||||
// saving progress to database
|
||||
newProgress.save().then((progress) => {
|
||||
return apiHelper.sendReturn(response, {
|
||||
id: progress.id
|
||||
});
|
||||
}).catch((rejection) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
return apiHelper.sendApiError(response, 500, [rejection]);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /admin/api/v1/editprogress
|
||||
*
|
||||
* edit an existing progress entry
|
||||
*
|
||||
* post {
|
||||
* title - progress entry name
|
||||
* description - progress entry description
|
||||
* state - 0: backend service entry, 1: no support, 2: partial support, 3: fullly working
|
||||
* id - id of entry you want to edit
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if progress succesfull
|
||||
* id: String - id of the edited progress entry
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/admin/api/v1/editprogress', userMiddleware.adminAuthNeeded, (request, response) => {
|
||||
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
// parsing state and isGame to be valid
|
||||
const { title, description, id } = request.body;
|
||||
let { state } = request.body;
|
||||
let isGame = false;
|
||||
if (state != '1' && state != '2' && state != '3') {
|
||||
state = undefined;
|
||||
} else {
|
||||
state = parseInt(state);
|
||||
isGame = true;
|
||||
}
|
||||
|
||||
// updating progress in database
|
||||
progressList.progressListModel.findByIdAndUpdate(id, {
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
isGame
|
||||
}, (error, progress) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
id: progress.id
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// api 404
|
||||
router.use('/admin/api', (request, response) => {
|
||||
return apiHelper.sendApi404(response);
|
||||
});
|
||||
|
||||
// export the router
|
||||
module.exports = router;
|
||||
134
routes/blog.js
134
routes/blog.js
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
|
||||
blog.js -
|
||||
file for handling routes regarding blog posts.
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const logger = require('winston');
|
||||
const router = require('express').Router();
|
||||
const moment = require('moment');
|
||||
const apiHelper = require('../helpers/api');
|
||||
const utilHelper = require('../helpers/util');
|
||||
const blogPostModel = require('../models/blog-post').blogPostModel;
|
||||
const postAuthorModel = require('../models/post-author').postAuthorModel;
|
||||
|
||||
// display single blog post
|
||||
router.get('/news/:date/:title', (request, response) => {
|
||||
const date = request.params.date;
|
||||
const title = request.params.title;
|
||||
const title_lower = title.toLowerCase();
|
||||
|
||||
// date format YYYY-MM-DD
|
||||
if (/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date) && /([a-z]|[0-9]|-)+/.test(title_lower)) {
|
||||
// params are correct format
|
||||
blogPostModel.getPost(moment(date), title_lower, (error, post) => {
|
||||
// error exists or no post exists with the date and name
|
||||
if (error || !post) {
|
||||
logger.log('warn', `error: ${error} and post: ${post}`);
|
||||
return utilHelper.send404(response);
|
||||
}
|
||||
|
||||
// render blogpost
|
||||
post.postTemplate((error, postTemplate) => {
|
||||
if (error) {
|
||||
return utilHelper.send404(response);
|
||||
}
|
||||
|
||||
return response.render('post', {
|
||||
title,
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
post: postTemplate,
|
||||
user: utilHelper.templateReadyUser(request),
|
||||
locale: utilHelper.getLocale('US', 'en')
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// params are incorrect
|
||||
return utilHelper.send404(response);
|
||||
}
|
||||
});
|
||||
|
||||
// display latest blogposts
|
||||
router.get('/news', (request, response) => {
|
||||
// sort blogposts on date descending
|
||||
blogPostModel.find({}).sort({'meta.date': 'desc'}).exec((error, posts) => {
|
||||
if (error || !posts) {
|
||||
return utilHelper.send404(response);
|
||||
}
|
||||
|
||||
// makes posts template ready
|
||||
const postCollection = [];
|
||||
for (let i = 0, l = posts.length; i < l; i++) {
|
||||
postCollection.push(posts[i].postShortTemplate());
|
||||
}
|
||||
|
||||
return response.render('post-collection', {
|
||||
title: 'Pretendo | News',
|
||||
description: 'The Pretendo news page has a place for all of the update posts the Pretendo developer team has made. It contains everything you need to know about pretendo.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
posts: postCollection,
|
||||
user: utilHelper.templateReadyUser(request),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
page: 'news'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/listauthors
|
||||
*
|
||||
* gets a list of all authors
|
||||
*
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if author succesfull
|
||||
* authorList: Objects[{_id, name, description, image}] - list of authors with information
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/api/v1/listauthors', (request, response) => {
|
||||
postAuthorModel.find({}, (error, authors) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
authorList: authors
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/listblog
|
||||
*
|
||||
* gets a list of all posts
|
||||
*
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if post succesfull
|
||||
* postList: Objects[{_id, content, meta}] - list of posts with information
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/api/v1/listblog', (request, response) => {
|
||||
blogPostModel.find({}, (error, posts) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
apiHelper.sendReturn(response, {
|
||||
postList: posts
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// export router
|
||||
module.exports = router;
|
||||
16
routes/cemu.js
Normal file
16
routes/cemu.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const router = require('express').Router();
|
||||
const utilHelper = require('../helpers/util');
|
||||
|
||||
router.get('/cemu', (request, response) => {
|
||||
const locale = utilHelper.getLocale('US', 'en');
|
||||
|
||||
return response.render('cemu', {
|
||||
title: 'Pretendo | Cemu',
|
||||
description: locale.about.text,
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
16
routes/connecting.js
Normal file
16
routes/connecting.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const router = require('express').Router();
|
||||
const utilHelper = require('../helpers/util');
|
||||
|
||||
router.get('/connecting', (request, response) => {
|
||||
const locale = utilHelper.getLocale('US', 'en');
|
||||
|
||||
return response.render('connecting', {
|
||||
title: 'Pretendo | Connecting',
|
||||
description: locale.about.text,
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
|
||||
contact.js -
|
||||
file for handling routes regarding contact
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const logger = require('winston');
|
||||
const router = require('express').Router();
|
||||
const apiHelper = require('../helpers/api');
|
||||
const utilHelper = require('../helpers/util');
|
||||
const config = require('../config.json');
|
||||
const https = require('https');
|
||||
|
||||
// display contact page
|
||||
router.get('/contact', (request, response) => {
|
||||
return response.render('contact', {
|
||||
title: 'Pretendo | Contact',
|
||||
description: 'The Pretendo Contact page offers a way to ask questions and communicate with the Pretendo developer team. Its the perfect place to get answers.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
user: utilHelper.templateReadyUser(request),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
page: 'contact'
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/sendmessage
|
||||
*
|
||||
* registers a new admin user
|
||||
*
|
||||
* post {
|
||||
* email - email of sender
|
||||
* subject - subject of message
|
||||
* message - actual message
|
||||
* }
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if sending was successull
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/api/v1/sendmessage', (req, response) => {
|
||||
if (!req.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
|
||||
const { email, subject, message } = req.body;
|
||||
if (email && subject && message && message.length < 2000) {
|
||||
// request body has everything
|
||||
const postData = JSON.stringify({
|
||||
content: 'email: ' + email + ' \n subject: ' + subject + ' \n\n' + message
|
||||
});
|
||||
|
||||
// request object
|
||||
const request = https.request({
|
||||
hostname: config.contactWebhook.host,
|
||||
port: config.contactWebhook.port,
|
||||
path: config.contactWebhook.path,
|
||||
method : 'POST',
|
||||
headers : {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Length': postData.length
|
||||
}
|
||||
}, () => {
|
||||
// sends success
|
||||
return apiHelper.sendReturn(response, {});
|
||||
});
|
||||
|
||||
// error handling
|
||||
request.on('error', (error) => {
|
||||
logger.log('warn', 'request errored' + error);
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
});
|
||||
|
||||
// write post data to request
|
||||
request.write(postData);
|
||||
request.end();
|
||||
} else {
|
||||
// TODO give more detailed response
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// export router
|
||||
module.exports = router;
|
||||
16
routes/faq.js
Normal file
16
routes/faq.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const router = require('express').Router();
|
||||
const utilHelper = require('../helpers/util');
|
||||
|
||||
router.get('/faq', (request, response) => {
|
||||
const locale = utilHelper.getLocale('US', 'en');
|
||||
|
||||
return response.render('faq', {
|
||||
title: 'Pretendo | FAQ',
|
||||
description: locale.about.text,
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -9,22 +9,17 @@ for routes on the root path
|
||||
// imports
|
||||
const router = require('express').Router();
|
||||
const utilHelper = require('../helpers/util');
|
||||
const blogPostModel = require('../models/blog-post').blogPostModel;
|
||||
|
||||
// display home page
|
||||
router.get('/', (request, response) => {
|
||||
// needs callback because mongoose is inconsistent
|
||||
blogPostModel.latestPostsShortTemlate(2, (error, result) => {
|
||||
return response.render('home', {
|
||||
title: 'Pretendo | Home',
|
||||
description: 'Pretendo is a as close as possible recreation of the original Nintendo Network for 3ds and Wiiu. It is an replacement for if the original servers shut down.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
user: utilHelper.templateReadyUser(request),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
posts: result,
|
||||
page: 'home'
|
||||
});
|
||||
const locale = utilHelper.getLocale('US', 'en');
|
||||
|
||||
return response.render('home', {
|
||||
title: 'Pretendo | Home',
|
||||
description: locale.about.text,
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
212
routes/pnid.js
212
routes/pnid.js
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
|
||||
admin.js -
|
||||
file for handling admin api.
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const logger = require('winston');
|
||||
const router = require('express').Router();
|
||||
const passport = require('passport');
|
||||
const userMiddleware = require('../middleware/authentication');
|
||||
const apiHelper = require('../helpers/api');
|
||||
const utilHelper = require('../helpers/util');
|
||||
const mailer = require('../helpers/mailer');
|
||||
const config = require('../config.json');
|
||||
const Recaptcha = require('express-recaptcha').Recaptcha;
|
||||
const recaptcha = new Recaptcha(config.recaptcha.siteKey, config.recaptcha.secretKey);
|
||||
|
||||
// database models
|
||||
const PNID = require('../models/pnid');
|
||||
|
||||
// renders register page
|
||||
router.get('/pnid/register', (request, response) => {
|
||||
return response.render('pnid/register', {
|
||||
title: 'Pretendo | Register',
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
recaptcha_sitekey: config.recaptcha.siteKey
|
||||
});
|
||||
});
|
||||
// renders login page
|
||||
router.get('/pnid/login', (request, response) => {
|
||||
return response.render('pnid/login', {
|
||||
title: 'Pretendo | Login',
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale: utilHelper.getLocale('US', 'en')
|
||||
});
|
||||
});
|
||||
// renders password forget
|
||||
router.get('/pnid/forgotpassword', (request, response) => {
|
||||
return response.render('pnid/forgotpassword', {
|
||||
title: 'Pretendo | Forgot password',
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
user: utilHelper.templateReadyUser(request)
|
||||
});
|
||||
});
|
||||
// renders password reset page
|
||||
router.get('/pnid/passwordreset', (request, response) => {
|
||||
return response.render('passwordreset', {
|
||||
title: 'Pretendo | Password reset',
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
user: utilHelper.templateReadyUser(request)
|
||||
});
|
||||
});
|
||||
// logout
|
||||
router.get('/pnid/logout', userMiddleware.pnidAuthNeeded, (request, response) => {
|
||||
request.logout();
|
||||
return response.redirect('/');
|
||||
});
|
||||
// renders pnid dashboard
|
||||
router.get('/pnid/dashboard', userMiddleware.pnidAuthNeeded, (request, response) => {
|
||||
return response.render('pnid/dashboard', {
|
||||
title: 'Pretendo | Dash',
|
||||
description: 'On the Pretendo Network ID Dashboard you can manage your personal information and make new virtual consoles to use for login on your home console.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
user: utilHelper.templateReadyUser(request)
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/login
|
||||
*
|
||||
* signs user in
|
||||
*
|
||||
* post {
|
||||
* email
|
||||
* password
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean
|
||||
* username: undefined | string - only if login was successfull
|
||||
* role: undefined | string - role of user if login was successfull
|
||||
* errors: Strings[messages] - not yet :(
|
||||
* }
|
||||
*/
|
||||
// TODO make login somehow display errors in correct format.
|
||||
// middleware does the authentication work. this just returns a success
|
||||
router.post('/api/v1/login', passport.authenticate('PNIDStrategy'), function (request, response) {
|
||||
return apiHelper.sendReturn(response, {
|
||||
email: request.user.email,
|
||||
email_validated: request.user.email_validated,
|
||||
pnid: request.user.pnid.key
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/passwordreset
|
||||
*
|
||||
* signs user in
|
||||
*
|
||||
* post {
|
||||
* password
|
||||
* password_confirm
|
||||
* }
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.post('/api/v1/passwordreset', function (request, response) {
|
||||
//TODO make the actual password change
|
||||
return apiHelper.sendReturn(response, {});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/register
|
||||
*
|
||||
* registers a new admin user
|
||||
*
|
||||
* post {
|
||||
* username
|
||||
* password
|
||||
* }
|
||||
* return {
|
||||
* code: httpcode
|
||||
* success: boolean - true if register was successull
|
||||
* username: undefined | string - username if register was successfullW
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
|
||||
const multer = require('multer')();
|
||||
|
||||
router.post('/api/v1/register', multer.none(), recaptcha.middleware.verify, async (request, response) => {
|
||||
if (!request.body) {
|
||||
return apiHelper.sendApiGenericError(response);
|
||||
}
|
||||
|
||||
console.log(request.body);
|
||||
|
||||
return response.send(request.body);
|
||||
|
||||
if (request.recaptcha.error) {
|
||||
logger.log('warn', `[reCaptcha ERROR] ${request.recaptcha.error} | IP: ${request.ip} | Data: ${JSON.stringify(request.body)}`);
|
||||
return apiHelper.sendApiError(response, 500, ['Captcha error']);
|
||||
}
|
||||
|
||||
const { email, password, confirm_password, username } = request.body;
|
||||
|
||||
if (password !== confirm_password) {
|
||||
return apiHelper.sendApiError(response, 400, ['Passwords do not match']);
|
||||
}
|
||||
|
||||
const email_validation_code = await PNID.PNIDModel.generateEmailValidationCode();
|
||||
const email_validation_token = await PNID.PNIDModel.generateEmailValidationToken();
|
||||
|
||||
const newUser = new PNID.PNIDModel({
|
||||
email,
|
||||
email_validation_code,
|
||||
email_validation_token,
|
||||
password,
|
||||
pnid: {
|
||||
pid: await PNID.PNIDModel.generatePID(),
|
||||
username,
|
||||
username_lower: username.toLowerCase()
|
||||
}
|
||||
});
|
||||
|
||||
// saving to database
|
||||
newUser.save().then((user) => {
|
||||
mailer.send(
|
||||
user.get('email'),
|
||||
'[Pretendo Network] Please confirm your e-mail address',
|
||||
`Hello,
|
||||
Your Pretendo Network ID activation is almost complete. Please click the link below to confirm your e-mail address and complete the activation process.
|
||||
|
||||
https://account.pretendo.cc/account/email-confirmation?token${user.get('email_validation_token')}
|
||||
|
||||
If you are unable to connect to the above URL, please enter the following confirmation code on the device to which your Pretendo Network ID is linked.
|
||||
|
||||
<<Confirmation code: ${user.get('email_validation_code')}>>`
|
||||
);
|
||||
|
||||
return apiHelper.sendReturn(response, {
|
||||
email: user.email,
|
||||
email_validated: user.email_validated,
|
||||
pnid: user.pnid.pid
|
||||
});
|
||||
}).catch((rejection) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
logger.log('warn', rejection);
|
||||
return apiHelper.sendApiError(response, 500, [rejection]);
|
||||
});
|
||||
});
|
||||
|
||||
// export the router
|
||||
module.exports = router;
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
|
||||
progress.js -
|
||||
file for handling routes regarding progress
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const router = require('express').Router();
|
||||
const apiHelper = require('../helpers/api');
|
||||
const utilHelper = require('../helpers/util');
|
||||
const progressListModel = require('../models/progress-list').progressListModel;
|
||||
|
||||
// display progress
|
||||
router.get('/progress', (request, response) => {
|
||||
|
||||
progressListModel.find({}, (error, progress) => {
|
||||
if (error) {
|
||||
return utilHelper.send404(response);
|
||||
}
|
||||
|
||||
// filtering games and backend
|
||||
const games = progress.filter(i => i.isGame);
|
||||
const backends = progress.filter(i => !i.isGame);
|
||||
|
||||
return response.render('progress', {
|
||||
title: 'Pretendo | Progress',
|
||||
description: 'The Pretendo progress page shows the progress the Pretendo developer team made. It shows how much is working and if it can be used for the end user yet.',
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
games,
|
||||
backends,
|
||||
user: utilHelper.templateReadyUser(request),
|
||||
locale: utilHelper.getLocale('US', 'en'),
|
||||
page: 'progress'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* /api/v1/listprogress
|
||||
*
|
||||
* gets a list of progress
|
||||
*
|
||||
* return {
|
||||
* code: http code
|
||||
* success: boolean - true if progress succesfull
|
||||
* progressList: Objects[{_id, title, description, state}] - list of progress with information
|
||||
* errors: Strings[messages]
|
||||
* }
|
||||
*/
|
||||
router.get('/api/v1/listprogress', (request, response) => {
|
||||
progressListModel.find({}, (error, progress) => {
|
||||
// TODO format exception so it doesnt have a huge list of errors
|
||||
if (error) {
|
||||
return apiHelper.sendApiError(response, 500, [error]);
|
||||
}
|
||||
|
||||
apiHelper.sendReturn(response, {
|
||||
progressList: progress
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// export router
|
||||
module.exports = router;
|
||||
88
server.js
88
server.js
@@ -1,33 +1,13 @@
|
||||
/*
|
||||
process.on('uncaughtException', console.warn);
|
||||
|
||||
server.js -
|
||||
the file that contains the startup code
|
||||
|
||||
*/
|
||||
|
||||
// imports
|
||||
const logger = require('./logger');
|
||||
const express = require('express');
|
||||
const handlebars = require('express-handlebars');
|
||||
const session = require('express-session');
|
||||
const mongoStore = require('connect-mongo')(session);
|
||||
const mongoose = require('mongoose');
|
||||
const morgan = require('morgan');
|
||||
const logger = require('./logger');
|
||||
const config = require('./config.json');
|
||||
const utilHelper = require('./helpers/util');
|
||||
const passportconfig = require('./passport.config.js');
|
||||
|
||||
// setup console colors
|
||||
require('colors');
|
||||
|
||||
// fix database deprecation warnings
|
||||
mongoose.set('useCreateIndex', true);
|
||||
mongoose.set('useNewUrlParser', true);
|
||||
// setup database connection
|
||||
mongoose.connect(config.database.url);
|
||||
const connection = mongoose.connection;
|
||||
connection.on('error', console.error.bind(console, 'connection error:'));
|
||||
|
||||
// setup express
|
||||
const app = express();
|
||||
app.use(morgan('dev'));
|
||||
@@ -35,39 +15,12 @@ app.use(express.json());
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(session({
|
||||
secret: config.secrets.session,
|
||||
saveUninitialized: true,
|
||||
resave: true,
|
||||
cookie: {
|
||||
maxAge: 3600000,
|
||||
httpOnly: true//, // remove on production
|
||||
/*secure: true*/ // uncomment on production
|
||||
},
|
||||
// permanent session storing for MongoDB
|
||||
store: new mongoStore({
|
||||
mongooseConnection: connection,
|
||||
collection: 'sessions'
|
||||
})
|
||||
}));
|
||||
// setups up passportjs authentication
|
||||
passportconfig(app);
|
||||
|
||||
// handlebars templating setup
|
||||
const hbsEngine = handlebars({
|
||||
extname: '.hbs',
|
||||
layoutsDir: 'views',
|
||||
partialsDir: 'views/partials',
|
||||
helpers: {
|
||||
'if_eq': function(a, b, opts) {
|
||||
if(a == b) return opts.fn(this);
|
||||
else return opts.inverse(this);
|
||||
},
|
||||
'if_neq': function(a, b, opts) {
|
||||
if(a != b) return opts.fn(this);
|
||||
else return opts.inverse(this);
|
||||
}
|
||||
}
|
||||
partialsDir: 'views/partials'
|
||||
});
|
||||
app.engine('.hbs', hbsEngine);
|
||||
app.set('view engine', '.hbs');
|
||||
@@ -75,35 +28,38 @@ app.set('view engine', '.hbs');
|
||||
// locations and routes setup
|
||||
const locations = {
|
||||
home: require('./routes/home'),
|
||||
posts: require('./routes/blog'),
|
||||
admin: require('./routes/admin'),
|
||||
contact: require('./routes/contact'),
|
||||
progress: require('./routes/progress'),
|
||||
pnid: require('./routes/pnid')
|
||||
connecting: require('./routes/connecting'),
|
||||
cemu: require('./routes/cemu'),
|
||||
faq: require('./routes/faq'),
|
||||
};
|
||||
|
||||
// static files
|
||||
app.use('/assets', express.static('assets'));
|
||||
app.use('/', express.static('public'));
|
||||
app.use('/assets', express.static(__dirname + '/assets'));
|
||||
|
||||
// page map
|
||||
app.use('/', locations.home);
|
||||
app.use('/', locations.contact);
|
||||
app.use('/', locations.posts);
|
||||
app.use('/', locations.admin);
|
||||
app.use('/', locations.progress);
|
||||
app.use('/', locations.pnid);
|
||||
app.use('/', locations.connecting);
|
||||
app.use('/', locations.cemu);
|
||||
app.use('/', locations.faq);
|
||||
app.use((request, response) => {
|
||||
return utilHelper.send404(response);
|
||||
const locale = utilHelper.getLocale('US', 'en');
|
||||
|
||||
return response.status(404).render('404', {
|
||||
title: 'Pretendo | 404',
|
||||
description: locale.about.text,
|
||||
url: request.protocol + '://' + request.get('host') + request.originalUrl,
|
||||
baseurl: request.protocol + '://' + request.get('host'),
|
||||
locale
|
||||
});
|
||||
});
|
||||
|
||||
// 4 parameters required to read the error, cant help the eslint error
|
||||
app.use((error, request, response, next) => { // eslint-disable-line no-unused-vars
|
||||
logger.log('warn', error.stack);
|
||||
logger.error(error.stack);
|
||||
return response.status(500).send('Something broke!');
|
||||
});
|
||||
|
||||
// startup
|
||||
app.listen(config.http.port, () => {
|
||||
logger.log('debug', `started the server on port: ${config.http.port}`);
|
||||
console.log(`started the server on port: ${new String(config.http.port).green}`);
|
||||
logger.success(`started the server on port: ${config.http.port}`);
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
// fix database deprecation warnings
|
||||
mongoose.set('useCreateIndex', true);
|
||||
mongoose.set('useNewUrlParser', true);
|
||||
// setup database connection
|
||||
mongoose.connect(require('./config.json').database.url);
|
||||
const connection = mongoose.connection;
|
||||
connection.on('error', console.error.bind(console, 'connection error:'));
|
||||
|
||||
const adminUser = require('./models/admin-user');
|
||||
|
||||
|
||||
const newUser = new adminUser.adminUserModel({
|
||||
username: 'admin',
|
||||
password: 'rootroot'
|
||||
});
|
||||
|
||||
newUser.save().then(() => {
|
||||
console.log('success, user admin pass root');
|
||||
}).catch((rejection) => {
|
||||
console.log(rejection);
|
||||
});
|
||||
@@ -6,11 +6,13 @@
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-contact.css">
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> navbar }}
|
||||
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">ERROR</p>
|
||||
<p class="txt-highlight">404</p>
|
||||
<h1 class="txt-title">{{ locale.404.title }}</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
134
views/admin.hbs
134
views/admin.hbs
@@ -1,134 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
</head>
|
||||
<body>
|
||||
{{> nav-default }}
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<h1 class="txt-title">login</h1>
|
||||
<form action="/admin/api/v1/login" method="POST">
|
||||
<input type="text" name="username">
|
||||
<input type="text" name="password">
|
||||
<button>submit login</button>
|
||||
</form>
|
||||
|
||||
<div id="container">
|
||||
<h1 class="txt-title">register new admin</h1>
|
||||
<form action="/admin/api/v1/register" method="POST">
|
||||
<input type="text" name="username">
|
||||
<input type="text" name="password">
|
||||
<button>submit register</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">delete admin</h1>
|
||||
<form action="/admin/api/v1/removeadmin" method="POST">
|
||||
<p>id</p>
|
||||
<input type="text" name="id">
|
||||
<button>delete admin</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">new blogpost</h1>
|
||||
<form action="/admin/api/v1/newpost" method="POST">
|
||||
<p>content</p>
|
||||
<textarea name="content" cols="30" rows="10"></textarea>
|
||||
<p>short version</p>
|
||||
<textarea name="short" cols="30" rows="10"></textarea>
|
||||
<p>category</p>
|
||||
<input type="text" name="category">
|
||||
<p>title</p>
|
||||
<input type="text" name="title">
|
||||
<p>author id</p>
|
||||
<input type="text" name="author">
|
||||
<button>create post</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">edit blogpost</h1>
|
||||
<form action="/admin/api/v1/editpost" method="POST">
|
||||
<p>content</p>
|
||||
<textarea name="content" cols="30" rows="10"></textarea>
|
||||
<p>short version</p>
|
||||
<textarea name="short" cols="30" rows="10"></textarea>
|
||||
<p>id</p>
|
||||
<input type="text" name="id">
|
||||
<p>category</p>
|
||||
<input type="text" name="category">
|
||||
<p>title</p>
|
||||
<input type="text" name="title">
|
||||
<p>author id</p>
|
||||
<input type="text" name="author">
|
||||
<button>edit post</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">new blogpost author</h1>
|
||||
<form action="/admin/api/v1/newauthor" method="POST">
|
||||
<p>name</p>
|
||||
<input type="text" name="name">
|
||||
<p>description</p>
|
||||
<input type="text" name="description">
|
||||
<p>image link</p>
|
||||
<input type="text" name="image">
|
||||
<button>submit author</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">edit author info</h1>
|
||||
<form action="/admin/api/v1/editauthor" method="POST">
|
||||
<p>id</p>
|
||||
<input type="text" name="id">
|
||||
<p>name</p>
|
||||
<input type="text" name="name">
|
||||
<p>description</p>
|
||||
<input type="text" name="description">
|
||||
<p>image link</p>
|
||||
<input type="text" name="image">
|
||||
<button>edit author</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">new progress entry</h1>
|
||||
<form action="/admin/api/v1/newprogress" method="POST">
|
||||
<p>title</p>
|
||||
<input type="text" name="title">
|
||||
<p>description</p>
|
||||
<input type="text" name="description">
|
||||
<p>state</p>
|
||||
<p>0 - backend service<br>1 - no support<br>2 - partial support<br>3 - fully working</p>
|
||||
<input type="text" name="state">
|
||||
<button>submit progress</button>
|
||||
</form>
|
||||
|
||||
<h1 class="txt-title">edit progress entry</h1>
|
||||
<form action="/admin/api/v1/editprogress" method="POST">
|
||||
<p>id</p>
|
||||
<input type="text" name="id">
|
||||
<p>title</p>
|
||||
<input type="text" name="title">
|
||||
<p>description</p>
|
||||
<input type="text" name="description">
|
||||
<p>state</p>
|
||||
<p>0 - backend service<br>1 - no support<br>2 - partial support<br>3 - fully working</p>
|
||||
<input type="text" name="state">
|
||||
<button>edit progress</button>
|
||||
</form>
|
||||
|
||||
<br>
|
||||
<a href="/admin/api/v1/listadmins">List of admin users</a>
|
||||
<br>
|
||||
<a href="/api/v1/listblog">List of blog posts</a>
|
||||
<br>
|
||||
<a href="/api/v1/listprogress">List of progress</a>
|
||||
<br>
|
||||
<a href="/api/v1/listauthors">List of authors</a>
|
||||
<br>
|
||||
<a href="/admin/api/v1/check">Check if logged in</a>
|
||||
<br>
|
||||
<a href="/admin/api/v1/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
21
views/cemu.hbs
Normal file
21
views/cemu.hbs
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
</head>
|
||||
<body>
|
||||
{{> navbar }}
|
||||
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<h1 class="txt-title">Cemu</h1>
|
||||
<h2>Pretendo is currently not usable by the general public. When the network becomes public, this will contain steps on connecting to the network using Cemu</h2>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
21
views/connecting.hbs
Normal file
21
views/connecting.hbs
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
</head>
|
||||
<body>
|
||||
{{> navbar }}
|
||||
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<h1 class="txt-title">Connecting</h1>
|
||||
<h2>Pretendo is currently not usable by the general public. When the network becomes public, this will contain steps on connecting to the network</h2>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-contact.css">
|
||||
<!-- scripts -->
|
||||
<script src="/assets/js/api.js" defer></script>
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> nav-default }}
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ locale.contact.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.contact.title }}</h1>
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
<div id="successBox" class="success hide"></div>
|
||||
<form action="/api/v1/sendmessage" method="POST">
|
||||
<div class="input-container">
|
||||
<input type="text" id="email_input" name="email" placeholder="{{ locale.contact.email_placeholder }}">
|
||||
<input type="text" id="subject_input" name="subject" placeholder="{{ locale.contact.subject_placeholder }}">
|
||||
</div>
|
||||
<textarea name="message" id="message_input" placeholder="{{ locale.contact.message_placeholder }}"></textarea>
|
||||
</form>
|
||||
<div class="txt-center">
|
||||
<button onclick="sendMessage()" class="btn">{{ locale.contact.button_text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
62
views/faq.hbs
Normal file
62
views/faq.hbs
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-faq.css">
|
||||
</head>
|
||||
<body>
|
||||
{{> navbar }}
|
||||
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<h1 class="txt-title">FAQ</h1>
|
||||
<div class="faqs">
|
||||
<details>
|
||||
<summary>What is Pretendo?</summary>
|
||||
<p class="text">Pretendo is an open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles. Our goal is to preserve the online functionality of these consoles, to allow players to continue to play their favorite WiiU and 3DS games to their fullest capacity</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>How do can I use Pretendo?</summary>
|
||||
<p class="text">Pretendo is currently not in a state that is ready for public use. However, once it is you will be able to use Pretendo simply by running our homebrew patcher on your console</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Is there an ETA for XYZ?</summary>
|
||||
<p class="text">We do not have an exact ETA for any specific features or the project as a whole. There are many parts to the network, updates release for them individually whenever they are ready (for example updates to the account server are (almost always) independent on other parts of the network, such as the friends server). Due to that we cannot give an exact ETA</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Does Pretendo work on Cemu/emulators?</summary>
|
||||
<p class="text">
|
||||
Pretendo is designed for the WiiU and (eventually) 3DS. At this time the only emulator for these consoles with real online capability is Cemu. Cemu does not officially support custom servers, but it will still be possible to use Pretendo with Cemu
|
||||
<br>
|
||||
See the <a href="/cemu">Cemu</a> page for more details
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Will my existing NNIDs work on Pretendo?</summary>
|
||||
<p class="text">Sadly, no. The only ones with access to the data about your existing NNIDs is Nintendo. A NNID-to-PNID migration could theoretically be possible, but it would be a slow and lengthy process requiring sensitive user information</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>If I am account/console banned on Nintendo Network, will I stay banned when using Pretendo?</summary>
|
||||
<p class="text">Any account/console bans only exist server-side. This means any existing bans would not carry over to Pretendo. However Pretendo still reserves the right to issue a ban for our network if we see fit</p>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Will Pretendo support the Wii/Switch?</summary>
|
||||
<p class="text">The Wii already has custom servers provided by <a href="https://wiimmfi.de/">Wiimmfi</a>. We currently have no plans on supporting the Switch, as not only is it a completely different platform with a different online network, but Switch online is a paid service</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
121
views/home.hbs
121
views/home.hbs
@@ -6,7 +6,8 @@
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-home.css">
|
||||
</head>
|
||||
<body>
|
||||
{{> nav-default }}
|
||||
{{> navbar }}
|
||||
|
||||
<div class="contentWrapper">
|
||||
<header>
|
||||
<h1>{{ locale.home.header.title }}</h1>
|
||||
@@ -26,74 +27,74 @@
|
||||
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ locale.about.caption }}</p>
|
||||
<p class="txt-highlight">{{ locale.credits.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.credits.title }}</h1>
|
||||
<div class="cardProfiles">
|
||||
<!-- profile -->
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<img class="profileIcon" src="https://avatars3.githubusercontent.com/u/27011796">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
</div>
|
||||
</div><!--
|
||||
--><div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
</div>
|
||||
</div><!--
|
||||
--><div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
</div>
|
||||
</div><!--
|
||||
--><div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
</div>
|
||||
</div><!--
|
||||
--><div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
</div>
|
||||
</div><!--
|
||||
--><div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/117261997619412996/c0e34de348b8d45e7a16c0260173bc8c.webp?size=256">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">RedDucks</h2>
|
||||
<p>Project owner, lead developer, very violent young man. Splatoon obsessed as well.</p>
|
||||
<h2 class="txt-smallHeading">Jonathan Barrow (RedDucks)</h2>
|
||||
<p>Project owner and lead developer</p>
|
||||
<a href="https://github.com/RedDuckss" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard lessPadding cont-flex">
|
||||
<div class="flex-item">
|
||||
<p class="txt-highlight">{{ locale.latest_news.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.latest_news.title }}</h1>
|
||||
<p>{{ locale.latest_news.description }}</p>
|
||||
<a href="/news" class="btn">{{ locale.latest_news.button_text }}</a>
|
||||
</div>
|
||||
<div class="cont-flex">
|
||||
{{#each posts as |post| }}
|
||||
<div class="smallCardWidth">
|
||||
<div class="smallCard">
|
||||
<h3 class="txt-smallHeading">{{ post.title }}</h3>
|
||||
<p>{{ post.content }}</p>
|
||||
<a href="/news/{{ post.url }}" class="btn-alt">{{ ../locale.latest_news.news_button_text }}</a>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://avatars3.githubusercontent.com/u/32065563">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">Jemma (CaramelKat)</h2>
|
||||
<p>Miiverse research and development</p>
|
||||
<a href="https://github.com/CaramelKat" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://avatars3.githubusercontent.com/u/10212424">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">superwhiskers</h2>
|
||||
<p>crunch library development</p>
|
||||
<a href="https://github.com/superwhiskers" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://avatars1.githubusercontent.com/u/19657053">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">SuperMarioDaBom</h2>
|
||||
<p>Console and other system research</p>
|
||||
<a href="https://github.com/SuperMarioDaBom" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/326811693751468033/1064818534277b6cafbb4b53a9a36d3e.png?size=2048">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">Rambo6Glaz</h2>
|
||||
<p>Network installer and console research</p>
|
||||
<a href="https://github.com/NexoDevelopment" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://avatars2.githubusercontent.com/u/19330965">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">mrjvs</h2>
|
||||
<p>Web development and early eShop research</p>
|
||||
<a href="https://github.com/mrjvs" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://avatars1.githubusercontent.com/u/47010303">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">Benjamin Moss (kxpler442b)</h2>
|
||||
<p>Web development</p>
|
||||
<a href="https://github.com/kxpler442b" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="https://cdn.discordapp.com/avatars/186572995848830987/b55c0d4e7bfd792edf0689f83a25d8ea.png?size=2048">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">Kinnay</h2>
|
||||
<p>None of this would be possible without the research and help done by Kinnay on PRUDP and nex</p>
|
||||
<a href="https://github.com/Kinnay" target="_blank"><button class="btn small">GitHub</button></a>
|
||||
</div>
|
||||
</div>
|
||||
{{/each }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<nav class="userNav">
|
||||
<div class="userNavProfile">
|
||||
<img src="{{ user.user.pnid.avatar_url }}" alt="{{ user.user.pnid.username }}">
|
||||
<p class="name">{{ user.user.pnid.username }}</p>
|
||||
</div>
|
||||
<div class="links">
|
||||
<a class="navItem active" href="#">{{ locale.dashboard.navigation.links.account }}</a>
|
||||
<a class="navItem" href="#">{{ locale.dashboard.navigation.links.soon }}</a>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,22 +0,0 @@
|
||||
<nav class="navWrapper" id="nav">
|
||||
<a class="navItem brand{{#if_eq page 'home'}} active{{/if_eq}}" {{#if_neq page 'home'}}href="/"{{/if_neq}}>{{ locale.nav.brand }}</a>
|
||||
<a class="navItem{{#if_eq page 'news'}} active{{/if_eq}}" {{#if_neq page 'news'}}href="/news"{{/if_neq}}>{{ locale.nav.links.news }}</a>
|
||||
<a class="navItem{{#if_eq page 'progress'}} active{{/if_eq}}" {{#if_neq page 'progress'}}href="/progress"{{/if_neq}}>{{ locale.nav.links.progress }}</a>
|
||||
<a class="navItem{{#if_eq page 'contact'}} active{{/if_eq}}" {{#if_neq page 'contact'}}href="/contact"{{/if_neq}}>{{ locale.nav.links.contact }}</a>
|
||||
<div class="navSpread"></div>
|
||||
{{#if user.isLoggedIn }}
|
||||
<a class="navItem navProfile" href="/pnid/dashboard">
|
||||
<img src="{{ user.user.pnid.avatar_url }}" alt="{{ user.user.pnid.username }}">
|
||||
<p>{{ user.user.pnid.username }}</p>
|
||||
</a>
|
||||
<a class="navItem navBtn" href="/pnid/logout">logout</a>
|
||||
{{else}}
|
||||
<a class="navItem navBtnAlt" href="/pnid/login">{{ locale.nav.links.login }}</a>
|
||||
<a class="navItem navBtn" href="/pnid/register">{{ locale.nav.links.register }}</a>
|
||||
{{/if}}
|
||||
<a class="navItem hamburger" id="navToggle">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</a>
|
||||
</nav>
|
||||
14
views/partials/navbar.hbs
Normal file
14
views/partials/navbar.hbs
Normal file
@@ -0,0 +1,14 @@
|
||||
<nav class="navWrapper" id="nav">
|
||||
<a class="navItem brand" href="/">{{ locale.nav.brand }}</a>
|
||||
<a class="navItem" href="/connecting">Connecting</a>
|
||||
<a class="navItem" href="/cemu">Cemu</a>
|
||||
<a class="navItem" href="/faq">FAQ</a>
|
||||
<a class="navItem" href="https://github.com/PretendoNetwork/" target="_blank">GitHub</a>
|
||||
<a class="navItem" href="https://invite.gg/pretendo" target="_blank">Discord</a>
|
||||
<div class="navSpread"></div>
|
||||
<a class="navItem hamburger" id="navToggle">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</a>
|
||||
</nav>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-pnid.css">
|
||||
<!-- scripts -->
|
||||
<script src="/assets/js/api.js"></script>
|
||||
<script src="/assets/js/pnid.js"></script>
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> nav-default }}
|
||||
<div id="email_verification_popup" class="email_popup{{#if_eq user.user.email_validated true}} hide{{/if_eq}}"><p>{{ locale.verify_email_popup.text }}</p><span><button class="btn-alt small" onclick="toggleEditInfo();">{{ locale.verify_email_popup.change_button_text }}</button> <button class="btn small" onclick="email_resend();">{{ locale.verify_email_popup.repeat_button_text }}</button></span></div>
|
||||
<div class="contentWrapper userPage">
|
||||
{{> nav-dashboard }}
|
||||
<section class="contentCard">
|
||||
<p class="txt-highlight">{{ locale.dashboard.account.caption }}</p>
|
||||
<h1 class="txt-title small">{{ locale.dashboard.account.title }}</h1>
|
||||
<div class="infoContainer" id="userInfo">
|
||||
<p class="infoList"><span>Username:</span> {{ user.user.pnid.username }}</p>
|
||||
<p class="infoList"><span>Email:</span> {{ user.user.email }}</p>
|
||||
<button class="btn small btn-alt" onclick="toggleEditInfo();">Edit</button>
|
||||
</div>
|
||||
<div class="infoContainer hide" id="editInfo">
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
<p class="infoList"><span>Username:</span>
|
||||
<input type="text" class="inline" name="username_input" id="username_input" value="{{ user.user.pnid.username }}">
|
||||
</p>
|
||||
<p class="infoList"><span>Email:</span>
|
||||
<input type="text" class="inline" name="email_input" id="email_input" value="{{ user.user.email }}">
|
||||
</p>
|
||||
<button class="btn small" onclick="editInfo();">Save</button><a href="#" class="link-margin" onclick="toggleEditInfo();">Cancel</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> metatags }}
|
||||
<title>{{ title }}</title>
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-common.css">
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-auth.css">
|
||||
<script src="/assets/js/api.js" defer></script>
|
||||
</head>
|
||||
<body class="flex center">
|
||||
<div class="card">
|
||||
<h2 class="head">{{ locale.nav.brand }}</h2>
|
||||
<div class="content">
|
||||
<p class="txt-highlight">{{ locale.forgot.card.caption }}</p>
|
||||
<h1 class="txt-title small">{{ locale.forgot.card.title }}</h1>
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
<form method="POST">
|
||||
<label for="email_input">{{ locale.forgot.card.email_text}}</label>
|
||||
<input type="text" class="hasLabel" id="email_input" name="email_input">
|
||||
</form>
|
||||
<button class="btn btnMargin" onclick="passwordForgot();">{{ locale.forgot.card.button_text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> metatags }}
|
||||
<title>{{ title }}</title>
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-common.css">
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-auth.css">
|
||||
<script src="/assets/js/api.js" defer></script>
|
||||
</head>
|
||||
<body class="flex center">
|
||||
<div class="card">
|
||||
<h2 class="head">{{ locale.nav.brand }}</h2>
|
||||
<div class="content">
|
||||
<p class="txt-highlight">{{ locale.login.card.caption }}</p>
|
||||
<h1 class="txt-title small">{{ locale.login.card.title }}</h1>
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
<form method="POST">
|
||||
<label for="email">{{ locale.login.card.email_text }}</label>
|
||||
<input type="text" class="hasLabel" id="email_input" name="email">
|
||||
<label for="email">{{ locale.login.card.password_text }}</label>
|
||||
<input type="password" class="hasLabel" id="password_input" name="password">
|
||||
<p class="lowText">{{ locale.login.card.forgotpass_introduction }} <a href="/pnid/forgotpassword">{{ locale.login.card.forgotpass_text }}</a></p>
|
||||
</form>
|
||||
<button class="btn btnMargin" onclick="login();">{{ locale.login.card.button_text }}</button>
|
||||
<p class="lowText">{{ locale.login.card.link_introduction }} <a href="/pnid/register">{{ locale.login.card.link_text }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> metatags }}
|
||||
<title>{{ title }}</title>
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-common.css">
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-auth.css">
|
||||
<script src="/assets/js/api.js" defer></script>
|
||||
</head>
|
||||
<body class="flex center">
|
||||
<div class="card">
|
||||
<h2 class="head">{{ locale.nav.brand }}</h2>
|
||||
<div class="content">
|
||||
<p class="txt-highlight">{{ locale.password.card.caption }}</p>
|
||||
<h1 class="txt-title small">{{ locale.password.card.title }}</h1>
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
<form method="POST">
|
||||
<label for="password_input">{{ locale.password.card.password_text}}</label>
|
||||
<input type="password" class="hasLabel" id="password_input" name="password_input">
|
||||
<label for="password_confirm">{{ locale.password.card.password_confirm_text }}</label>
|
||||
<input type="password" class="hasLabel" id="password_confirm" name="password_confirm">
|
||||
</form>
|
||||
<button class="btn btnMargin" onclick="passwordReset();">{{ locale.password.card.button_text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,147 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> metatags }}
|
||||
<title>{{ title }}</title>
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-common.css">
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-auth.css">
|
||||
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
<script src="/assets/js/api.js" defer></script>
|
||||
|
||||
<!-- date picker -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.css">
|
||||
</head>
|
||||
<body class="flex center">
|
||||
<div class="card">
|
||||
<h2 class="head">{{ locale.nav.brand }}</h2>
|
||||
<div class="content">
|
||||
<p class="txt-highlight">{{ locale.register.card.caption }}</p>
|
||||
<h1 class="txt-title small">{{ locale.register.card.title }}</h1>
|
||||
<div id="errorBox" class="error hide"></div>
|
||||
|
||||
<form id="registerForm" method="POST" action="http://account.pretendo.cc/v1/api/people/registerwp">
|
||||
<label for="birthdate">{{ locale.register.card.birthdate_text }}</label>
|
||||
<input required type="text" class="hasLabel" id="birthdate_input" name="birthdate">
|
||||
<label for="gender">{{ locale.register.card.gender_text }}</label>
|
||||
<input required type="text" class="hasLabel" id="gender_input" name="gender" list="gender-list" autocomplete="new-password">
|
||||
<datalist id="gender-list">
|
||||
<option value="M"></option>
|
||||
<option value="F"></option>
|
||||
</datalist>
|
||||
<label for="email">{{ locale.register.card.email_text }}</label>
|
||||
<input required type="text" class="hasLabel" id="email_input" name="email">
|
||||
<label for="username">{{ locale.register.card.username_text }}</label>
|
||||
<input required type="text" class="hasLabel" id="username_input" name="username" minlength="6" maxlength="16">
|
||||
<label for="password">{{ locale.register.card.password_text }}</label>
|
||||
<input required type="password" class="hasLabel" id="password_input" name="password">
|
||||
<label for="confirm_password">{{ locale.register.card.password_confirm_text }}</label>
|
||||
<input required type="password" class="hasLabel" id="confirm_password_input" name="confirm_password">
|
||||
|
||||
<div class="g-recaptcha"
|
||||
data-callback="capchaFilled"
|
||||
data-expired-callback="capchaExpired"
|
||||
data-sitekey="{{ recaptcha_sitekey }}"></div>
|
||||
|
||||
<input disabled type="submit" value="{{ locale.register.card.button_text }}" class="btn btnMargin"></input>
|
||||
</form>
|
||||
|
||||
<p class="lowText">{{ locale.register.card.link_introduction }} <a href="/pnid/login">{{ locale.register.card.link_text }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#birthdate_input').datepicker({
|
||||
autoPick: true,
|
||||
format: 'yyyy-mm-dd',
|
||||
startView: 2
|
||||
});
|
||||
|
||||
const form = document.getElementById('registerForm');
|
||||
form.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
|
||||
// https://stackoverflow.com/a/34296482
|
||||
const elements = [].filter.call(form.elements, element => {
|
||||
return !(element.type in ['checkbox', 'radio']) || element.checked;
|
||||
})
|
||||
.filter(element => { return !!element.name })
|
||||
.filter(element => { return !element.disabled });
|
||||
|
||||
const data = {};
|
||||
for ({name, value} of elements) {
|
||||
data[name] = value;
|
||||
}
|
||||
|
||||
if (!data['g-recaptcha-response'] || data['g-recaptcha-response'].trim() === '') {
|
||||
alert('Fill in the captcha');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!data.email ||
|
||||
!data.username ||
|
||||
!data.password ||
|
||||
!data.confirm_password ||
|
||||
data.email.trim() === '' ||
|
||||
data.username.trim() === '' ||
|
||||
data.password.trim() === '' ||
|
||||
data.confirm_password.trim() === ''
|
||||
) {
|
||||
// Handle error, there are missing fields
|
||||
console.log(data);
|
||||
alert('error')
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.password.trim() !== data.confirm_password.trim()) {
|
||||
alert('Passwords do not match');
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch(`http://account.pretendo.cc/v1/api/people/${data.username}`, {
|
||||
headers: {
|
||||
'x-nintendo-client-id': 'a2efa818a34fa16b8afbc8a74eba3eda',
|
||||
'x-nintendo-client-secret': 'c91cdb5658bd4954ade78533a339cf9a'
|
||||
}
|
||||
});
|
||||
const text = await response.text();
|
||||
|
||||
if (text.trim() !== '') {
|
||||
const body = (new window.DOMParser()).parseFromString(text, 'text/xml');
|
||||
const error = body;
|
||||
alert('Username already in use');
|
||||
return; // Handle errors here
|
||||
}
|
||||
|
||||
const params = elements.map(({name, value}) => {
|
||||
return encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
||||
}).join('&');
|
||||
|
||||
console.log(params);
|
||||
|
||||
postAjax(form.action, params, {
|
||||
load: event => {
|
||||
console.log(event.target.responseText)
|
||||
},
|
||||
error: event => {
|
||||
console.log(event)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function capchaFilled() {
|
||||
document.querySelector('input[type="submit"]').removeAttribute('disabled');
|
||||
}
|
||||
|
||||
function capchaExpired() {
|
||||
document.querySelector('input[type="submit"]').setAttribute('disabled', true);
|
||||
}
|
||||
</script>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-news.css">
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> nav-default }}
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard lessPadding">
|
||||
<p class="txt-highlight">{{ locale.news.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.news.title }}</h1><!--
|
||||
{{#each posts as |post| }}
|
||||
--><article class="smallCard">
|
||||
<h3 class="txt-smallHeading">{{ post.title }}</h3>
|
||||
<p>{{ post.content }}</p>
|
||||
<a class="btn-alt" href="/news/{{ post.url }}">{{ ../locale.news.button_text }}</a>
|
||||
</article><!--
|
||||
{{/each}}
|
||||
-->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> nav-default }}
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ post.category }} — {{ post.dateString }}</p>
|
||||
<h1 class="txt-title">{{ post.title }}</h1>
|
||||
{{{ post.content }}}
|
||||
<div class="profile">
|
||||
<img class="profileIcon" src="{{ post.author.profilePicture }}">
|
||||
<div class="profileText">
|
||||
<h2 class="txt-smallHeading">{{ post.author.name }}</h2>
|
||||
<p>{{ post.author.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{> head-common }}
|
||||
<!-- page specific -->
|
||||
<link rel="stylesheet" href="/assets/css/pretendo-progress.css">
|
||||
</head>
|
||||
<body class="flex">
|
||||
{{> nav-default }}
|
||||
<div class="contentWrapper bgLinesSmall">
|
||||
<div class="bgExtrude"></div>
|
||||
<section class="bigCardWrapper">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ locale.progress.summary.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.progress.summary.title }}</h1>
|
||||
<p>{{ locale.progress.summary.text }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="bigCardWrapper double">
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ locale.progress.compatibility.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.progress.compatibility.title }}</h1>
|
||||
{{#each games }}
|
||||
<div class="listItem state{{ this.state }}">
|
||||
<h2>{{ this.title }}</h2>
|
||||
<p>{{ this.description }}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="bigCard">
|
||||
<p class="txt-highlight">{{ locale.progress.misc.caption }}</p>
|
||||
<h1 class="txt-title">{{ locale.progress.misc.title }}</h1>
|
||||
{{#each backends }}
|
||||
<div class="listItem">
|
||||
<h2>{{ this.title }}</h2>
|
||||
<p>{{ this.description }}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{> footer-default }}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user