diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f6d6f01 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +# web javascript causes a lot of eslint warnings +assets/js/*.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index cd25f9c..5a7a948 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,10 @@ "node": true, "commonjs": true, "es6": true - }, + }, + "globals": { + "document": true + }, "parserOptions": { "ecmaVersion": 2017 }, diff --git a/assets/css/icons.css b/assets/css/icons.css deleted file mode 100644 index a5da83e..0000000 --- a/assets/css/icons.css +++ /dev/null @@ -1,30 +0,0 @@ -@font-face { - font-family: 'icomoon'; - src: url('/assets/fonts/icomoon.eot?ybqupz'); - src: url('/assets/fonts/icomoon.eot?ybqupz#iefix') format('embedded-opentype'), - url('/assets/fonts/icomoon.ttf?ybqupz') format('truetype'), - url('/assets/fonts/icomoon.woff?ybqupz') format('woff'), - url('/assets/fonts/icomoon.svg?ybqupz#icomoon') format('svg'); - font-weight: normal; - font-style: normal; -} - -[class^="pretendo-icon-"], [class*=" pretendo-icon-"] { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'icomoon' !important; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - font-size: 1em; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.pretendo-icon-pretendo-logo:before { - content: "\e900"; -} diff --git a/assets/css/pretendo-auth.css b/assets/css/pretendo-auth.css new file mode 100644 index 0000000..1eb5953 --- /dev/null +++ b/assets/css/pretendo-auth.css @@ -0,0 +1,45 @@ +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; +} \ No newline at end of file diff --git a/assets/css/pretendo-common.css b/assets/css/pretendo-common.css new file mode 100644 index 0000000..9727b06 --- /dev/null +++ b/assets/css/pretendo-common.css @@ -0,0 +1,379 @@ +/*******************************************************/ +/* copyrighted by pretendo. */ +/* website design by jipfr, mrjvs and benthehuman */ +/*******************************************************/ + +/* fonts */ +@import url('https://fonts.googleapis.com/css?family=Roboto:400,700'); + +/* setup */ +body { + margin: 0; + font-family: 'Roboto', Arial, Helvetica, sans-serif; + font-weight: 400; + background: #efefef; + overflow-x: hidden; +} +body.flex { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +body.flex .contentWrapper { + flex: 1; +} + +/* common header */ +nav.navWrapper { + background: #673db6; + color: white; + display: flex; + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 10; + justify-content: center; +} +nav.navWrapper .navItem { + flex: 0; + padding: 15px 20px; + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + font-size: 1em; + cursor: pointer; + align-self: stretch; +} +nav.navWrapper .navItem:hover, nav.navWrapper .navItem.active { + color: white; + text-decoration: none; + background: rgba(0, 0, 0, 0.07); +} +nav.navWrapper .navItem.brand { + font-weight: 600; + background: #5231a7; + padding: 15px 40px; + color: white; +} +nav.navWrapper .navBtn, nav.navWrapper .navBtnAlt { + flex: 0; + align-self: center; + padding: 5px 30px; + color: white; + text-decoration: none; + font-size: 1em; + cursor: pointer; + background-color: #512da8; + border-radius: 5px; + margin: 0 10px; + box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07); +} +nav.navWrapper .navBtnAlt { + color: #3d1c8a; + background-color: white; +} +nav.navWrapper .navBtn:hover { + background-color: #52339b; +} +nav.navWrapper .navBtnAlt:hover { + background-color: rgba(255, 255, 255, 0.745); + color: #54359b; +} +nav.navWrapper .navItem:nth-last-child(2) { + margin-right: 20px; +} +nav.navWrapper .navProfile { + align-self: stretch; + display: flex; + align-items: center; + padding: 0 20px; +} +nav.navWrapper .navProfile img { + border-radius: 50%; + width: 2em; height: 2em; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.5); +} +nav.navWrapper .navProfile p { + padding: 0 0 0 10px; +} +nav.navWrapper .navSpread { + flex: 1; +} + +/* mobile nav */ +nav.navWrapper .navItem.hamburger { + display: none; + position: absolute; + right: 0; + top: 0; +} +@media screen and (max-width: 720px) { + nav.navWrapper { + flex-direction: column; + box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07); + } + nav.navWrapper .navItem:not(.brand) { + display: none; + } + nav.navWrapper.open .navItem, nav.navWrapper .navItem.hamburger { + display: block; + } + nav.navWrapper.open .navItem.navBtn, nav.navWrapper.open .navItem.navBtnAlt { + padding: .5em 0; + width: 12em; + text-align: center; + margin: .5em 0; + } + nav.navWrapper .navItem:nth-last-child(2) { + margin-right: 0; + } + nav.navWrapper.open .navItem.navProfile { + align-self: center; + display: flex; + } +} + +/* content wrapper */ +.contentWrapper { + position: relative; + width: 100%; +} +nav.navWrapper ~ .contentWrapper:nth-child(2) { + margin-top: 3rem; +} +.contentWrapper .bgExtrude { + position: absolute; + top: 0; + left: 0; + width: 100%; + background: #673db6; + height: 10rem; + z-index: 2; +} +.contentWrapper.bgLines::before, .contentWrapper.bgLines::after, +.contentWrapper.bgLinesSmall::before, .contentWrapper.bgLinesSmall::after { + content: ""; + position: absolute; + z-index: 3; + width: 0; + height: 0; + border-style: solid; + border-width: 100vh 0 0 200vw; + border-color: transparent transparent transparent #673db6; + left: 0; + bottom: 0; +} +.contentWrapper.bgLines::after, .contentWrapper.bgLinesSmall::after { + border-width: 0 0 100vh 200vw; + border-color: transparent transparent #9575CD transparent; + right: 0; + left: auto; + z-index: 2; +} +.contentWrapper.bgLinesSmall::after { + border-width: 0 0 18vh 150vw; +} +.contentWrapper.bgLinesSmall::before { + border-width: 20vh 0 0 150vw; +} +.contentWrapper > * { + z-index: 3; + position: relative; +} + +/* footer */ +footer { + background: #673db6; + padding: 2em 1em; +} +footer .content { + background: white; + border-radius: 5px; + padding: 1em; + text-align: center; + box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07); + color: #707070; +} +footer .content span { + color: black; +} + +/* big card */ +section.bigCardWrapper { + padding: 20px; +} +section.bigCardWrapper .bigCard { + max-width: 80vw; + margin: auto; + background-color: white; + box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07); + padding: 5vw 12vw; + box-sizing: border-box; + color: #707070; +} +section.bigCardWrapper .bigCard.lessPadding { + padding: 4vw 5vw; +} +section.bigCardWrapper .bigCard.bigCardPartWrapper { + display: flex; +} + + +/* text styles */ +.txt-highlight { + color: #673db6; + margin-bottom: 0; + font-size: 1rem; + font-weight: bold; +} +.txt-title { + color: #191919; + margin-top: 10px; + margin-bottom: 15px; + font-size: 2.5em; +} +.txt-title.small { + font-size: 2em; +} +.txt-smallHeading { + font-size: 1.2em; + font-weight: bold; + color: #383838; +} +.txt-center { + text-align: center; +} + +/* components - buttons */ +a.btn, a.btn-alt, +a.btn:focus, a.btn-alt:focus, +a.btn:hover, a.btn-alt:hover { + text-decoration: none; +} +button, .btn, .btn-alt { + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + font-family: 'Roboto'; + font-size: 1em; + cursor: pointer; +} +.btn, button { + background: #673db6; + color: white; + padding: 12px 80px; + border-radius: 10px; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.17); +} +.btn-alt { + background: white; + padding: 10px 45px; + color: #673db6; + border: 1px solid #673db6; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.07); + border-radius: 5px; +} +.btn:hover, button:hover, .btn-alt:hover { + background: #553099; + color: white; +} + +/* components - profiles */ +/* profiles */ +.cardProfiles { + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} +.profile { + min-width: 250px; + width: 50%; + max-width: 550px; + margin-bottom: 10px; + display: flex; +} +.profileIcon { + margin-right: 10px; + width: 80px; height: 80px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.5); +} +.profileText { + width: calc(100% - 130px); + min-width: 200px; + display: flex; + flex-direction: column; + justify-content: center; +} +.profileText h2 { + margin: 0; +} +.profileText p { + margin-top: 5px; +} + +/* components - inputs */ + +input::placeholder, textarea::placeholder { + color: #bebebe; + opacity: 1; +} + +input[type="text"], input[type="password"], textarea { + display: inline-block; + width: 100%; + padding: 1em 2em; + border-style: solid; + border-color: rgb(165, 165, 165); + border-width: 1px; + border-radius: 8px; + margin: 1em 0; + font-family: 'Roboto', Arial, Helvetica, sans-serif; + font-weight: 400; + font-size: 1rem; + box-shadow: 0 3px 10px 0px rgba(0, 0, 0, 0.07); + transition: border-color .1s; + box-sizing: border-box; + resize: none; +} + +label { + font-size: 1em; + margin: 2em 0 .2em .2em; + display: block; +} + +input[type="text"].hasLabel, input[type="password"].hasLabel { + margin: 0; +} + +textarea { + height: 12rem; +} + +input[type="text"]:focus, input[type="password"]:focus, textarea:focus { + border-color: rgb(103, 61, 182); + box-shadow: 0 3px 10px 0px rgba(103, 61, 182, 0.07); +} + +/* helper classes */ +.cont-flex { + display: flex; +} +.flex-item { + flex: 1; +} + +@media screen and (max-width: 500px) { + section.bigCardWrapper { + padding: 0; + padding-bottom: 3em; + } + section.bigCardWrapper .bigCard { + max-width: 100vw; + padding: 50px; + } +} \ No newline at end of file diff --git a/assets/css/pretendo-contact.css b/assets/css/pretendo-contact.css new file mode 100644 index 0000000..2f41e97 --- /dev/null +++ b/assets/css/pretendo-contact.css @@ -0,0 +1,15 @@ +.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; + } +} \ No newline at end of file diff --git a/assets/css/pretendo-home.css b/assets/css/pretendo-home.css new file mode 100644 index 0000000..33eedac --- /dev/null +++ b/assets/css/pretendo-home.css @@ -0,0 +1,93 @@ +/* home header */ + +header { + text-align: center; + color: white; + background: #673db6; + padding: 5rem 1rem; +} +header hr { + width: 175px; + height: 2px; + background: white; + display: inline-block; + border: 0; + margin: 0 auto; + display: block; +} +header h1 { + font-size: 3.5em; + font-weight: 400; + letter-spacing: 0.25em; + margin: 0; + margin-bottom: 10px; + text-transform: uppercase; +} +header h3 { + font-weight: normal; + max-width: 80%; + display: inline-block; + font-size: 1.1rem; +} + +/* news cards */ +.smallCard { + padding: 20px; + border: 1px solid #9474cc; border-radius: 10px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.07); + position: relative; + margin-left: 3em; + height: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; +} +.smallCard h3 { + font-size: 1.5em; + margin: 0; +} +.smallCard p { + flex: 1; +} + +/* specific */ +.smallCardWidth { + width: 100%; + max-width: 300px; +} + +@media screen and (max-width: 1050px) { + .bigCard.cont-flex { + flex-direction: column; + } + .bigCard > .flex-item { + margin-bottom: 3em; + } + .smallCardWidth .smallCard { + margin-left: 0; + margin: 1em; + } + .bigCard.cont-flex > .cont-flex { + align-items: center; + justify-content: center; + } +} + +@media screen and (max-width: 888px) { + .profile { + width: 100%; + max-width: 100%; + min-width: 100%; + } + .smallCard { + width: 100%; + max-width: 250px; + height: auto; + } + .bigCard.cont-flex > .cont-flex { + flex-direction: column; + } + header h1 { + font-size: 2.5em; + } +} \ No newline at end of file diff --git a/assets/css/pretendo-news.css b/assets/css/pretendo-news.css new file mode 100644 index 0000000..3b25a0a --- /dev/null +++ b/assets/css/pretendo-news.css @@ -0,0 +1,26 @@ +/* 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; + } +} \ No newline at end of file diff --git a/assets/css/pretendo-pnid.css b/assets/css/pretendo-pnid.css new file mode 100644 index 0000000..fc94d28 --- /dev/null +++ b/assets/css/pretendo-pnid.css @@ -0,0 +1,72 @@ +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; +} + +/* 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; +} \ No newline at end of file diff --git a/assets/css/pretendo-progress.css b/assets/css/pretendo-progress.css new file mode 100644 index 0000000..39f98c4 --- /dev/null +++ b/assets/css/pretendo-progress.css @@ -0,0 +1,68 @@ +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; + } +} \ No newline at end of file diff --git a/assets/css/pretendo.css b/assets/css/pretendo.css deleted file mode 100644 index fdc7dec..0000000 --- a/assets/css/pretendo.css +++ /dev/null @@ -1,105 +0,0 @@ - -/* - -pretendo.css - -main css file of the pretendo website - -*/ - -/* - -for home page only. -sets the background - -*/ -.hp-background { - - -webkit-filter: brightness(30%) grayscale(100%); - filter: brightness(30%) grayscale(100%); - min-height: 100%; - min-width: 100%; - width: 100%; - height: 100%; - position: fixed; - top: 0; - left: 0; - -} - -/* - -homepage container for the title and stuff - -*/ -.hp-container { - - display: flex; - position: relative; - flex-direction: column; - justify-content: center; - text-align: left; - left: 0%; - z-index: 100; - -} - -/* - -title text - -*/ -.title { - - font-family: "Overpass Mono", monospace; - font-size: 500%; - line-height: 0; - color: white; - -} - -/* - -main text - -*/ -.text { - - font-family: "Space Mono", monospace; - font-size: 150%; - color: white; - -} - -/* - -font awesome icon container stylings - -*/ -.icons { - - margin-top: 10px; - font-size: 1.75em; - -} - -.icons > a:not(:first-child) > .icon, .icons > .icon:not(:first-child) { - - margin-left: 20px; - -} - -.icon { - - -webkit-transition: color 1s; - transition: color 1s; - color: white; - border: 0; - -} - -.icon:hover { - - color: black; - -} - \ No newline at end of file diff --git a/assets/fonts/icomoon.eot b/assets/fonts/icomoon.eot deleted file mode 100644 index 01476ee..0000000 Binary files a/assets/fonts/icomoon.eot and /dev/null differ diff --git a/assets/fonts/icomoon.svg b/assets/fonts/icomoon.svg deleted file mode 100644 index 1e28f37..0000000 --- a/assets/fonts/icomoon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - \ No newline at end of file diff --git a/assets/fonts/icomoon.ttf b/assets/fonts/icomoon.ttf deleted file mode 100644 index 89ee3a7..0000000 Binary files a/assets/fonts/icomoon.ttf and /dev/null differ diff --git a/assets/fonts/icomoon.woff b/assets/fonts/icomoon.woff deleted file mode 100644 index e022c7a..0000000 Binary files a/assets/fonts/icomoon.woff and /dev/null differ diff --git a/assets/images/bg1.gif b/assets/images/bg1.gif deleted file mode 100644 index e7c94c2..0000000 Binary files a/assets/images/bg1.gif and /dev/null differ diff --git a/assets/images/bg2.gif b/assets/images/bg2.gif deleted file mode 100644 index 17aa4e8..0000000 Binary files a/assets/images/bg2.gif and /dev/null differ diff --git a/assets/js/auth.js b/assets/js/auth.js new file mode 100644 index 0000000..f41d421 --- /dev/null +++ b/assets/js/auth.js @@ -0,0 +1,26 @@ +function postAjax(url, data, success) { + var params = (typeof data == 'string') ? data : Object.keys(data).map(function (k) { + return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) + }).join('&'); + + var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); + xhr.open('POST', url); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.send(params); + return xhr; +} + +function login() { + var xhr = postAjax('/api/v1/login', { + email: document.getElementById('email_input').value, + password: document.getElementById('password_input').value + }); + xhr.onload = function () { + console.log(xhr.responseText); + response = JSON.parse(xhr.responseText); + if (response.success === true) { + location.pathname = '/pnid/dashboard'; + } + } +} \ No newline at end of file diff --git a/assets/js/index.js b/assets/js/index.js deleted file mode 100644 index 36cd927..0000000 --- a/assets/js/index.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-env browser */ - -/* - -index.js - -the main javascript file - -*/ - -// set some variables -const bg = document.getElementById('hp-bg'); -let index = 0; -const imageList = [ '/assets/images/bg1.gif', '/assets/images/bg2.gif' ]; - -// set the first image -bg.src = imageList[index]; -index++; - -setInterval(() => { - - if (index === imageList.length) { - index = 0; - } - - bg.src = imageList[index]; - - index++; -}, 5000); \ No newline at end of file diff --git a/assets/js/nav.js b/assets/js/nav.js new file mode 100644 index 0000000..a5e7277 --- /dev/null +++ b/assets/js/nav.js @@ -0,0 +1,3 @@ +document.getElementById('navToggle').onclick = () => { + document.getElementById('nav').classList.toggle('open'); +}; \ No newline at end of file diff --git a/config.example.json b/config.example.json index 98a25e6..89ab907 100644 --- a/config.example.json +++ b/config.example.json @@ -12,5 +12,9 @@ }, "secrets": { "session": "session secret here" + }, + "recaptcha": { + "siteKey": "abcd", + "secretKey": "1234" } } diff --git a/helpers/util.js b/helpers/util.js index cff7eba..9509f1e 100644 --- a/helpers/util.js +++ b/helpers/util.js @@ -6,10 +6,26 @@ small commonly used utilities */ // shows 404 template. takes express response object -function sendDefault404(res) { +function send404(res) { res.status(404).send('404'); } +function templateReadyUser(req) { + // normal user logged in + const isLoggedIn = req.user ? req.user.role != 'admin' : false; + const user = req.user; + return { + isLoggedIn, + user + }; +} + +function getLocales() { + return require('../locales/US_en.json'); +} + module.exports = { - sendDefault404 + send404, + templateReadyUser, + getLocales }; \ No newline at end of file diff --git a/middleware/admin-authentication.js b/middleware/admin-authentication.js deleted file mode 100644 index 777fb45..0000000 --- a/middleware/admin-authentication.js +++ /dev/null @@ -1,28 +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 authentication is optional -function authOptional(req, res, next) { - return next(); -} - -module.exports = { - adminAuthNeeded, - authOptional -}; \ No newline at end of file diff --git a/middleware/authentication.js b/middleware/authentication.js new file mode 100644 index 0000000..54fb7a4 --- /dev/null +++ b/middleware/authentication.js @@ -0,0 +1,48 @@ +/* + +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 +}; \ No newline at end of file diff --git a/models/blog-post.js b/models/blog-post.js index fbf3804..2c2aaeb 100644 --- a/models/blog-post.js +++ b/models/blog-post.js @@ -45,7 +45,7 @@ const blogPostSchema = new mongoose.Schema({ date: { type: Date, default: () => { - return new Date(moment(new Date(), 'YYYY-MM-DD')); + return moment(moment().format('YYYY-MM-DD')); } }, category: { @@ -72,7 +72,7 @@ blogPostSchema.methods.postShortTemplate = function() { return { content: this.short, title: this.name, - url: moment(this.meta.date, 'YYYY-MM-DD') + '/' + this.meta.slug + url: moment(this.meta.date).format('YYYY-MM-DD') + '/' + this.meta.slug }; }; @@ -85,13 +85,13 @@ blogPostSchema.statics.getPost = function(date, slug, callback) { 'meta.slug': slug }, callback); }; -// not tested + blogPostSchema.statics.latestPostsShortTemlate = function(amount, callback) { blogPostModel.find({}).sort({'meta.date': 'desc'}).exec(function(err, posts) { if (err) return callback(err); - let out = []; - for (let i = 0, l = posts.length; i < ( amount+1 < l ? amount+1 : l); i++) - out += posts[i].postShortTemplate(); + const out = []; + for (let i = 0, l = posts.length; i < ( amount+1 < l ? amount : l); i++) + out.push(posts[i].postShortTemplate()); callback(err, out); }); }; diff --git a/models/pnid.js b/models/pnid.js new file mode 100644 index 0000000..e92b242 --- /dev/null +++ b/models/pnid.js @@ -0,0 +1,118 @@ +/* + +pnid.js - +file containing the model for pretendo network id's + +*/ + +// imports +const mongoose = require('mongoose'); +const uniqueValidator = require('mongoose-unique-validator'); +const bcrypt = require('bcrypt'); + +// admin user database layout +const PNIDSchema = new mongoose.Schema({ + email: { + type: String, + required: [true, 'Email is required.'], + unique: true, + trim: true, + validate: [validateEmail, 'Please fill a valid email address'] + }, + email_validated: { + type: Boolean, + default: false + }, + // 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: { + key: { + type: String // not sure what this should be + }, + pid: { + type: String, + unique: true + } + }, + consoles: [] +}); + +function validateEmail(email) { + // eslint throws "unnecesary character escape" + const re = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/; // eslint-disable-line + return re.test(email); +} + +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 + }); +}; + +PNIDSchema.statics.hashPasswordPrimary = function(password, pid) { + const buff1 = require('python-struct').pack(' { 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) => { @@ -42,14 +44,57 @@ module.exports = (app) => { }); } )); + passport.use('PNIDStrategy', new LocalStrategy({ + usernameField: 'email' + }, (email, password, done) => { + // find user in database + PNIDModel.findByEmail(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) { - done(err, user); + if (err || !user) { + PNIDModel.findById(id, function(err, user) { + done(err, user); + }); + } else { + done(err, user); + } }); }); }; diff --git a/routes/admin.js b/routes/admin.js index 5332ca9..a8e1647 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -10,7 +10,8 @@ const router = require('express').Router(); const passport = require('passport'); const moment = require('moment'); const apiHelper = require('../helpers/api'); -const adminUserMiddleware = require('../middleware/admin-authentication'); +const utilHelper = require('../helpers/util'); +const userMiddleware = require('../middleware/authentication'); // database models const adminUser = require('../models/admin-user'); @@ -45,6 +46,7 @@ router.get('/admin', (req, res) => { router.post('/admin/api/v1/login', passport.authenticate('adminUserStrategy'), function (req, res) { apiHelper.sendReturn(res, { username: req.user.username, + locales: utilHelper.getLocales(), role: req.user.role ? req.user.role : undefined }); }); @@ -67,7 +69,7 @@ router.post('/admin/api/v1/login', passport.authenticate('adminUserStrategy'), f * errors: Strings[messages] * } */ -router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthNeeded, (req, res) => { +router.post('/admin/api/v1/register', userMiddleware.adminAuthNeeded, (req, res) => { if (!req.body) { // no post body apiHelper.sendApiGenericError(res); @@ -81,10 +83,10 @@ router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthNeeded, (req, }); // saving to database - newUser.save().then(() => { + newUser.save().then((user) => { apiHelper.sendReturn(res, { - username: req.user.username, - role: req.user.role ? req.user.role : undefined + username: user.username, + role: user.role ? user.role : undefined }); return; }).catch((rejection) => { @@ -109,7 +111,7 @@ router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthNeeded, (req, * errors: Strings[messages] * } */ -router.post('/admin/api/v1/removeadmin', adminUserMiddleware.adminAuthNeeded, (req, res) => { +router.post('/admin/api/v1/removeadmin', userMiddleware.adminAuthNeeded, (req, res) => { if (!req.body) { // no post body apiHelper.sendApiGenericError(res); @@ -136,7 +138,7 @@ router.post('/admin/api/v1/removeadmin', adminUserMiddleware.adminAuthNeeded, (r * errors: Strings[messages] * } */ -router.get('/admin/api/v1/listadmins', adminUserMiddleware.adminAuthNeeded, (req, res) => { +router.get('/admin/api/v1/listadmins', userMiddleware.adminAuthNeeded, (req, res) => { adminUser.adminUserModel.find({}, (err, admins) => { // TODO format exception so it doesnt have a huge list of errors if (err) return apiHelper.sendApiError(res, 500, [err]); @@ -167,7 +169,7 @@ router.get('/admin/api/v1/listadmins', adminUserMiddleware.adminAuthNeeded, (req * errors: Strings[messages] * } */ -router.get('/admin/api/v1/check', adminUserMiddleware.authOptional, (req, res) => { +router.get('/admin/api/v1/check', userMiddleware.authOptional, (req, res) => { apiHelper.sendReturn(res, { isAuthed: req.user ? true : false, role: req.user ? (req.user.role ? req.user.role : undefined) : undefined @@ -185,7 +187,7 @@ router.get('/admin/api/v1/check', adminUserMiddleware.authOptional, (req, res) = * errors: Strings[messages] * } */ -router.get('/admin/api/v1/logout', adminUserMiddleware.adminAuthNeeded, (req, res) => { +router.get('/admin/api/v1/logout', userMiddleware.adminAuthNeeded, (req, res) => { req.logout(); apiHelper.sendReturn(res, {}); }); @@ -210,7 +212,7 @@ router.get('/admin/api/v1/logout', adminUserMiddleware.adminAuthNeeded, (req, re * errors: Strings[messages] * } */ -router.post('/admin/api/v1/newpost', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/newpost', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); @@ -233,7 +235,7 @@ router.post('/admin/api/v1/newpost', adminUserMiddleware.adminAuthNeeded, functi // saving post to database newBlogPost.save().then((post) => { apiHelper.sendReturn(res, { - url: moment(post.meta.date, 'YYYY-MM-DD') + '/' + post.meta.slug + 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 @@ -262,7 +264,7 @@ router.post('/admin/api/v1/newpost', adminUserMiddleware.adminAuthNeeded, functi * errors: Strings[messages] * } */ -router.post('/admin/api/v1/editpost', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/editpost', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); @@ -299,7 +301,7 @@ router.post('/admin/api/v1/editpost', adminUserMiddleware.adminAuthNeeded, funct * errors: Strings[messages] * } */ -router.post('/admin/api/v1/newauthor', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/newauthor', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); @@ -340,7 +342,7 @@ router.post('/admin/api/v1/newauthor', adminUserMiddleware.adminAuthNeeded, func * errors: Strings[messages] * } */ -router.post('/admin/api/v1/editauthor', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/editauthor', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); @@ -377,7 +379,7 @@ router.post('/admin/api/v1/editauthor', adminUserMiddleware.adminAuthNeeded, fun * errors: Strings[messages] * } */ -router.post('/admin/api/v1/newprogress', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/newprogress', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); @@ -429,7 +431,7 @@ router.post('/admin/api/v1/newprogress', adminUserMiddleware.adminAuthNeeded, fu * errors: Strings[messages] * } */ -router.post('/admin/api/v1/editprogress', adminUserMiddleware.adminAuthNeeded, function (req, res) { +router.post('/admin/api/v1/editprogress', userMiddleware.adminAuthNeeded, function (req, res) { if (!req.body) return apiHelper.sendApiGenericError(res); diff --git a/routes/blog.js b/routes/blog.js index 7284e92..ce8d6ce 100644 --- a/routes/blog.js +++ b/routes/blog.js @@ -7,6 +7,7 @@ file for handling routes regarding blog posts. // imports 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; @@ -17,7 +18,7 @@ router.get('/news/:date/:title', (req, res) => { // date format YYYY-MM-DD if (/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(req.params.date) && /([a-z]|[0-9]|-)+/.test(req.params.title.toLowerCase())) { // params are correct format - blogPostModel.getPost(new Date(req.params.date), req.params.title.toLowerCase(), (err, post) => { + blogPostModel.getPost(moment(req.params.date), req.params.title.toLowerCase(), (err, post) => { // error exists or no post exists with the date and name if (err || !post) { console.log('error: ' + err + ' and post: ' + post); @@ -28,7 +29,9 @@ router.get('/news/:date/:title', (req, res) => { post.postTemplate((err, postTemplate) => { if (err) return utilHelper.send404(res); res.render('post', { - post: postTemplate + post: postTemplate, + user: utilHelper.templateReadyUser(req), + locales: utilHelper.getLocales() }); }); }); @@ -53,7 +56,10 @@ router.get('/news', (req, res) => { } res.render('post-collection', { - posts: postCollection + posts: postCollection, + user: utilHelper.templateReadyUser(req), + locales: utilHelper.getLocales(), + page: 'news' }); }); }); diff --git a/routes/contact.js b/routes/contact.js index 26c8bd3..d569e10 100644 --- a/routes/contact.js +++ b/routes/contact.js @@ -8,12 +8,17 @@ file for handling routes regarding contact // imports 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', (req, res) => { - res.render('contact'); + res.render('contact', { + user: utilHelper.templateReadyUser(req), + locales: utilHelper.getLocales(), + page: 'contact' + }); }); /* diff --git a/routes/home.js b/routes/home.js index 5af1ab2..8d338cc 100644 --- a/routes/home.js +++ b/routes/home.js @@ -8,10 +8,20 @@ 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('/', (req, res) => { - res.render('home'); + // needs callback because mongoose is inconsistent + blogPostModel.latestPostsShortTemlate(2, (err, result) => { + res.render('home', { + user: utilHelper.templateReadyUser(req), + locales: utilHelper.getLocales(), + posts: result, + page: 'home' + }); + }); }); // export the router diff --git a/routes/pnid.js b/routes/pnid.js new file mode 100644 index 0000000..e5641c4 --- /dev/null +++ b/routes/pnid.js @@ -0,0 +1,130 @@ +/* + +admin.js - +file for handling admin api. + +*/ + +// imports +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 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', recaptcha.middleware.render, (req, res) => { + res.render('register', { + captcha: res.recaptcha, + locales: utilHelper.getLocales() + }); +}); +// renders login page +router.get('/pnid/login', (req, res) => { + res.render('login', { + locales: utilHelper.getLocales() + }); +}); +// logout +router.get('/pnid/logout', userMiddleware.pnidAuthNeeded, (req, res) => { + req.logout(); + res.redirect('/'); +}); +// renders pnid dashboard +router.get('/pnid/dashboard', userMiddleware.pnidAuthNeeded, (req, res) => { + res.render('dashboard', { + locales: utilHelper.getLocales(), + user: utilHelper.templateReadyUser(req) + }); +}); + +/* +* /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 (req, res) { + apiHelper.sendReturn(res, { + email: req.user.email, + email_validated: req.user.email_validated, + pnid: req.user.pnid.key + }); +}); + +/* +* /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] +* } +*/ +router.post('/api/v1/register', recaptcha.middleware.verify, async (req, res) => { + if (!req.body) { + // no post body + apiHelper.sendApiGenericError(res); + return; + } + /*if (req.recaptcha.error) { + apiHelper.sendApiError(res, 500, ['Captcha error']); + return; + }*/ + + const { email, password } = req.body; + const newUser = new PNID.PNIDModel({ + email, + password, + pnid: { + key: 'abcd', + pid: await PNID.PNIDModel.generatePID() + } + }); + + // TODO verify password + + // saving to database + newUser.save().then((user) => { + apiHelper.sendReturn(res, { + email: user.email, + email_validated: user.email_validated, + pnid: user.pnid.key + }); + return; + }).catch((rejection) => { + // TODO format exception so it doesnt have a huge list of errors + console.log(rejection); + apiHelper.sendApiError(res, 500, [rejection]); + return; + }); +}); + +// export the router +module.exports = router; diff --git a/routes/progress.js b/routes/progress.js index 254ae6e..ea3face 100644 --- a/routes/progress.js +++ b/routes/progress.js @@ -9,7 +9,6 @@ file for handling routes regarding progress const router = require('express').Router(); const apiHelper = require('../helpers/api'); const utilHelper = require('../helpers/util'); -const staticText = require('../static-text.json'); const progressListModel = require('../models/progress-list').progressListModel; // display progress @@ -25,7 +24,9 @@ router.get('/progress', (req, res) => { res.render('progress', { games, backends, - summary: staticText.progressSummary + user: utilHelper.templateReadyUser(req), + locales: utilHelper.getLocales(), + page: 'progress' }); }); }); diff --git a/server.js b/server.js index 416e809..32381d6 100644 --- a/server.js +++ b/server.js @@ -29,6 +29,7 @@ connection.on('error', console.error.bind(console, 'connection error:')); // setup express const app = express(); +app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(session({ secret: config.secrets.session, @@ -49,11 +50,22 @@ app.use(session({ passportconfig(app); // handlebars templating setup -app.engine('.hbs', handlebars({ +const hbsEngine = handlebars({ extname: '.hbs', layoutsDir: 'views', - partialsDir: 'views/partials' -})); + 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); + } + } +}); +app.engine('.hbs', hbsEngine); app.set('view engine', '.hbs'); // locations and routes setup @@ -62,7 +74,8 @@ const locations = { posts: require('./routes/blog'), admin: require('./routes/admin'), contact: require('./routes/contact'), - progress: require('./routes/progress') + progress: require('./routes/progress'), + pnid: require('./routes/pnid') }; // static files @@ -73,6 +86,7 @@ app.use('/', locations.contact); app.use('/', locations.posts); app.use('/', locations.admin); app.use('/', locations.progress); +app.use('/', locations.pnid); app.use((req, res) => { utilHelper.send404(res); }); diff --git a/setupAdminUser.js b/setupAdminUser.js index 3b4fe24..6a8066c 100644 --- a/setupAdminUser.js +++ b/setupAdminUser.js @@ -1,8 +1,18 @@ +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: 'root' + password: 'rootroot' }); newUser.save().then(() => { diff --git a/views/admin.hbs b/views/admin.hbs index c328c18..a3c663c 100644 --- a/views/admin.hbs +++ b/views/admin.hbs @@ -1,127 +1,134 @@ - {{> head }} + {{> head-common }} + - {{> navbar }} + {{> nav-default }} +
+
+
+
+

login

+
+ + + +
-

Admin panel here

-

register

-
- - - -
- -

login

-
- - - -
+
+

register new admin

+
+ + + +
-

delete admin

-
-

id

- - -
+

delete admin

+
+

id

+ + +
-

create blog post

-
-

content

- -

short version

- -

category

- -

title

- -

author id

- - -
+

new blogpost

+
+

content

+ +

short version

+ +

category

+ +

title

+ +

author id

+ + +
-

edit blog post

-
-

content

- -

short version

- -

id

- -

category

- -

title

- -

author id

- - -
+

edit blogpost

+
+

content

+ +

short version

+ +

id

+ +

category

+ +

title

+ +

author id

+ + +
-

new author

-
-

name

- -

description

- -

image link

- - -
+

new blogpost author

+
+

name

+ +

description

+ +

image link

+ + +
-

edit author

-
-

id

- -

name

- -

description

- -

image link

- - -
+

edit author info

+
+

id

+ +

name

+ +

description

+ +

image link

+ + +
-

new progress entry

-
-

title

- -

description

- -

state

-

0 - backend service
1 - no support
2 - partial support
3 - fully working

- - -
+

new progress entry

+
+

title

+ +

description

+ +

state

+

0 - backend service
1 - no support
2 - partial support
3 - fully working

+ + +
-

edit progress entry

-
-

id

- -

title

- -

description

- -

state

-

0 - backend service
1 - no support
2 - partial support
3 - fully working

- - -
+

edit progress entry

+
+

id

+ +

title

+ +

description

+ +

state

+

0 - backend service
1 - no support
2 - partial support
3 - fully working

+ + +
-
- List of admin users -
- List of blog posts -
- List of progress -
- List of authors -
- Check if logged in -
- Logout - - {{> footer }} +
+ List of admin users +
+ List of blog posts +
+ List of progress +
+ List of authors +
+ Check if logged in +
+ Logout +
+
+
+
+ {{> footer-default }} diff --git a/views/contact.hbs b/views/contact.hbs index 51fd7a7..c23d666 100644 --- a/views/contact.hbs +++ b/views/contact.hbs @@ -1,21 +1,31 @@ - {{> head }} - - {{> navbar }} - -

contact form

-
-

content

- -

subject

- -

email

- - -
- - {{> footer }} + {{> head-common }} + + + + + {{> nav-default }} +
+
+
+
+

ERROR

+

{{ locales.contact.title }}

+
+
+ + +
+ +
+ +
+
+
+
+
+ {{> footer-default }} diff --git a/views/dashboard.hbs b/views/dashboard.hbs new file mode 100644 index 0000000..807d58c --- /dev/null +++ b/views/dashboard.hbs @@ -0,0 +1,19 @@ + + + + {{> head-common }} + + + + + {{> nav-default }} +
+ {{> nav-dashboard }} +
+

ERROR

+

ERROR

+
+
+ {{> footer-default }} + + diff --git a/views/home.hbs b/views/home.hbs index 612d121..2ebba25 100644 --- a/views/home.hbs +++ b/views/home.hbs @@ -1,46 +1,103 @@ - - + - - - - {{> head }} - - pretendo - - - - - - - -
- -

pretendo

- -
- an accurate-as-possible server emulation project for
- nintendo consoles that use nintendo network -
- -
- - - - - - -
- + + {{> head-common }} + + + + + {{> nav-default }} +
+
+

ERROR

+
+

ERROR

+
+
+
+
+
+

ERROR

+

{{ locales.about.title }}

+

{{ locales.about.text }}

+
+
- - - +
+
+

ERROR

+

{{ locales.credits.title }}

+
+ +
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+ +
+

RedDucks

+

Project owner, lead developer, very violent young man. Splatoon obsessed as well.

+
+
+
+
+
+
+
+
+

ERROR

+

ERROR

+

ERROR

+ +
+
+ {{#each posts }} +
+
+

{{ this.title }}

+

{{ this.content }}

+ ERROR +
+
+ {{/each }} +
+
+
+
+ {{> footer-default }} + \ No newline at end of file diff --git a/views/login.hbs b/views/login.hbs new file mode 100644 index 0000000..1b77efe --- /dev/null +++ b/views/login.hbs @@ -0,0 +1,27 @@ + + + + {{> head-common }} + + + + + +
+

{{ locales.nav.brand }}

+
+

ERROR

+

{{ locales.login_card.title }}

+
+ + + + +
+ +

ERROR ERROR

+
+
+ {{> footer-default }} + + diff --git a/views/partials/footer-default.hbs b/views/partials/footer-default.hbs new file mode 100644 index 0000000..970956d --- /dev/null +++ b/views/partials/footer-default.hbs @@ -0,0 +1,5 @@ +
+
+

{{ locales.footer.text }}

+
+
\ No newline at end of file diff --git a/views/partials/footer.hbs b/views/partials/footer.hbs deleted file mode 100644 index f8b5f0e..0000000 --- a/views/partials/footer.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
-

© 2018 Pretendo

-
diff --git a/views/partials/head-common.hbs b/views/partials/head-common.hbs new file mode 100644 index 0000000..83b1491 --- /dev/null +++ b/views/partials/head-common.hbs @@ -0,0 +1,6 @@ +{{> metatags }} +ERROR + + + + \ No newline at end of file diff --git a/views/partials/head.hbs b/views/partials/head.hbs deleted file mode 100644 index 10f9d38..0000000 --- a/views/partials/head.hbs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/views/partials/metatags.hbs b/views/partials/metatags.hbs new file mode 100644 index 0000000..9bce706 --- /dev/null +++ b/views/partials/metatags.hbs @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/views/partials/nav-dashboard.hbs b/views/partials/nav-dashboard.hbs new file mode 100644 index 0000000..1ff4e15 --- /dev/null +++ b/views/partials/nav-dashboard.hbs @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/views/partials/nav-default.hbs b/views/partials/nav-default.hbs new file mode 100644 index 0000000..2f6f316 --- /dev/null +++ b/views/partials/nav-default.hbs @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/views/partials/navbar.hbs b/views/partials/navbar.hbs deleted file mode 100644 index 8b13789..0000000 --- a/views/partials/navbar.hbs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/views/post-collection.hbs b/views/post-collection.hbs index ca42724..d6a6ef8 100644 --- a/views/post-collection.hbs +++ b/views/post-collection.hbs @@ -1,7 +1,29 @@ -

Latest posts

-{{#each posts }} -{{ this.title }} -

{{ this.content }}

-link -

-{{/each}} \ No newline at end of file + + + + {{> head-common }} + + + + + {{> nav-default }} +
+
+
+
+

ERROR

+

ERROR

+

{{ this.title }}

+

{{ this.content }}

+ ERROR +
+
+
+
+ {{> footer-default }} + + \ No newline at end of file diff --git a/views/post.hbs b/views/post.hbs index e20f425..b02d8d4 100644 --- a/views/post.hbs +++ b/views/post.hbs @@ -1,22 +1,28 @@ - {{> head }} - - {{> navbar }} - -

{{ post.title }}

-

{{ post.date }}

-

{{ post.category }}

- -
- {{{ post.content }}} -
- -

{{ post.author.name }} - {{ post.author.description }}

- - - {{> footer }} - + {{> head-common }} + + + + {{> nav-default }} +
+
+
+
+

{{ post.category }} — {{ post.date }}

+

{{ post.title }}

+ {{{ post.content }}} +
+ +
+

{{ post.author.name }}

+

{{ post.author.description }}

+
+
+
+
+
+ {{> footer-default }} diff --git a/views/progress.hbs b/views/progress.hbs index 63019f7..5b14dd9 100644 --- a/views/progress.hbs +++ b/views/progress.hbs @@ -1,15 +1,44 @@ -

progress summary

-

{{ summary }}

-
-

game support

-{{#each games }} -{{ this.title }} -

{{ this.description }}

-

state: {{ this.state }}

-{{/each}} -

-

backend support

-{{#each backends }} -{{ this.title }} -

{{ this.description }}

-{{/each}} \ No newline at end of file + + + + {{> head-common }} + + + + + {{> nav-default }} +
+
+
+
+

ERROR

+

ERROR

+

{{ summary }}

+
+
+
+
+

ERROR

+

ERROR

+ {{#each games }} +
+

{{ this.title }}

+

{{ this.description }}

+
+ {{/each}} +
+
+

ERROR

+

ERROR

+ {{#each backends }} +
+

{{ this.title }}

+

{{ this.description }}

+
+ {{/each}} +
+
+
+ {{> footer-default }} + + diff --git a/views/register.hbs b/views/register.hbs new file mode 100644 index 0000000..cb9ca7c --- /dev/null +++ b/views/register.hbs @@ -0,0 +1,31 @@ + + + + {{> head-common }} + + + + + +
+

{{ locales.nav.brand }}

+
+

{{ locales.register_card.caption }}

+

{{ locales.register_card.title }}

+
+ + + + + + + + +
+ +

ERROR ERROR

+
+
+ {{> footer-default }} + +