added settings loading to API to fix caching, added site init and onboarding, added settings templates to make site set up easier

This commit is contained in:
Ro 2020-06-20 18:13:23 -07:00
parent e6b3917c51
commit cf89b48d17
21 changed files with 455 additions and 195 deletions

1
.gitignore vendored
View file

@ -12,6 +12,7 @@ site/settings.json
site/folks.json
site/pages.json
site/tags.json
site/_backup
brain/models/_backup/
/_maintenance/
*.DS_Store

View file

@ -5,7 +5,9 @@ const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const fs = require('fs-extra');
const _ = require('lodash');
//const crypto = require('crypto'); // for setting up new accounts
const crypto = require('crypto'); // for setting up new accounts
const secret_key = '58d5aeec3c604e2837aef70bc1606f35131ab8fea9731925558f5acfaa00da60';
const moment = require('moment');
/**
* Get Auth Status
@ -75,6 +77,76 @@ router.post('/login', function (req, res) {
});
});
/**
* Initial Site Setup
*/
router.post('/init', function (req, res) {
let body = req.body;
let re = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
// check email
if (!re.test(body.new_member_email)) {
res.json({
type: DataEvent.API_INIT_LAME,
message: 'Need a valid email address'
});
}
//check handle is being passed
if (body.new_member_handle === null || body.new_member_handle === '') {
res.json({
type: DataEvent.API_INIT_LAME,
message: 'No handle. Kinda need that.'
});
}
// check password match
if (
body.new_member_pass !== body.new_member_pass2 ||
body.new_member_pass === '' ||
body.new_member_pass2 === ''
) {
res.json({
type: DataEvent.API_INIT_LAME,
message: 'Passwords do not match.'
});
}
if (body.new_member_title === null || body.new_member_title === '') {
res.json({
type: DataEvent.API_INIT_LAME,
message: 'No title. Gotta call it something.'
});
}
let key = crypto
.createHash('sha256')
.update(body.new_member_pass + secret_key)
.digest('hex');
// set up config files
fs.readJson('site/init/settings-template.json').then(fresh => {
fresh.global.title = body.new_member_title;
fs.writeJSON('site/settings.json', fresh);
});
fs.readJson('site/init/folks-template.json').then(folks => {
folks[0].id = 1;
folks[0].handle = body.new_member_handle;
folks[0].email = body.new_member_email;
folks[0].password = bcrypt.hashSync(body.new_member_pass, bcrypt.genSaltSync(10), null);
folks[0].key = key;
folks[0].role = 'hnic';
folks[0].created = moment(Date.now()).format();
folks[0].updated = moment(Date.now()).format();
fs.writeJSON('site/folks.json', folks);
});
fs.writeJson('site/tags.json', { tags: [] });
res.json({
type: DataEvent.API_INIT_GOOD,
message: 'All Set Up'
});
});
//router.post('/logout', function(req, res) {});
module.exports = router;

View file

@ -13,9 +13,6 @@ const settings = new Settings();
const _ = require('lodash');
const uploadPath =
'./public/assets/images/blog/' + moment().format('YYYY') + '/' + moment().format('MM');
fs.ensureDir(uploadPath, () => {
// dir has now been created, including the directory it is to be placed in
});
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadPath);

View file

@ -1,7 +1,7 @@
import * as DataEvent from '../../../src/com/events/DataEvent';
import Auth from '../../data/Auth';
import Render from '../../data/Render';
import SettingsData from '../../data/Settings';
import Settings, { SETTINGS_FILE, SETTINGS_FOLKS } from '../../data/Settings';
import Navigation from '../../data/Navigation';
import Book from '../../data/Book';
const express = require('express');
@ -10,12 +10,11 @@ const multer = require('multer');
const fs = require('fs-extra');
const moment = require('moment');
const _ = require('lodash');
const settings = require('../../../site/settings.json');
const folks = require('../../../site/folks.json');
//const folks = require('../../../site/folks.json');
const auth = new Auth();
const render = new Render();
const book = new Book();
const settingsData = new SettingsData();
const settings = new Settings();
const nav = new Navigation();
const uploadPath =
'./public/assets/images/user/' + moment().format('YYYY') + '/' + moment().format('MM');
@ -41,7 +40,7 @@ var background_upload = multer({
router.post('/sync', (req, res) => {
auth.authCheck(req)
.then(() => {
settingsData
settings
.sync(req, res)
.then(() => {
res.json({
@ -92,10 +91,10 @@ router.post('/nav-sync', (req, res) => {
router.post('/publish-pages', (req, res) => {
auth.authCheck(req)
.then(() => {
book.getPage()
.then(pages => {
getBookData()
.then(result => {
render
.publishAll(pages, settings.global.theme)
.publishAll(result.pages, result.settings.global.theme)
.then(response => {
res.json({
type: response.type,
@ -133,6 +132,9 @@ router.post('/publish-pages', (req, res) => {
router.post('/add-avatar', avatar_upload, (req, res) => {
if (req.session.user) {
let user = req.session.user;
settings
.load(SETTINGS_FOLKS)
.then(folks => {
let found = _.find(folks, { handle: user.handle });
if (found) {
var index = found.id - 1;
@ -147,6 +149,13 @@ router.post('/add-avatar', avatar_upload, (req, res) => {
url: '/' + image
});
}
})
.catch(() => {
res.json({
type: DataEvent.REQUEST_LAME,
message: 'Members Not found'
});
});
} else {
res.json({
type: DataEvent.REQUEST_LAME,
@ -161,6 +170,9 @@ router.post('/add-avatar', avatar_upload, (req, res) => {
router.post('/add-feature-background', background_upload, (req, res) => {
if (req.session.user) {
settings
.load(SETTINGS_FILE)
.then(settings => {
var path = req.files[0].path;
var image = path.substr(7, path.length);
settings.background = '/' + image;
@ -170,6 +182,10 @@ router.post('/add-feature-background', background_upload, (req, res) => {
message: 'Background Uploaded',
url: '/' + image
});
})
.catch(err => {
console.log('ERROR', err);
});
} else {
res.json({
type: DataEvent.REQUEST_LAME,
@ -178,3 +194,19 @@ router.post('/add-feature-background', background_upload, (req, res) => {
}
});
module.exports = router;
function getBookData() {
return new Promise((resolve, reject) => {
let getSettings = settings.load(SETTINGS_FILE);
let getBook = book.getPage();
Promise.all([getSettings, getBook])
.then(result => {
const [settings, pages] = result;
let data = { settings: settings, pages: pages };
resolve(data);
})
.catch(err => {
reject(err);
});
});
}

View file

@ -29,7 +29,7 @@ export default class Book {
getPage(id) {
return new Promise((resolve, reject) => {
fh.create()
.paths('content/pages')
.paths("content/pages")
.ext('md')
.find()
.then(files => {
@ -66,7 +66,7 @@ export default class Book {
* @parameter body: object that contains all page information
* @parameter id: identifier for page being edited
* @parameter task: type of task being performed - listed in DataEvents Class
* @parameter user: object containin user information
* @parameter user: object contain user information
*/
editPage(body, id, task, user) {
return new Promise((resolve, reject) => {

View file

@ -145,11 +145,14 @@ export default class Render {
tag_list: item.tag_list
}
);
fs.ensureDir('public/tags', () => {
fs.writeFile('public/tags/' + item.slug + '.html', file, err => {
// throws an error, you could also catch it here
if (err) {
response = { type: DataEvent.TAG_PAGES_NOT_RENDERED, message: err };
response = {
type: DataEvent.TAG_PAGES_NOT_RENDERED,
message: err
};
reject(response);
}
// success case, the file was saved
@ -159,6 +162,7 @@ export default class Render {
};
resolve(response);
});
});
}
})
.catch(err => {

View file

@ -51,6 +51,7 @@ export default class Utils {
let archive = [];
for (let index = 0; index < pages.length; index++) {
let page = pages[index].metadata;
if (page.layout !== 'index') {
let year = moment(page.created).format('YYYY');
if (!_.find(years, { year: year })) {
years.push({ year: year, count: 1 });
@ -58,6 +59,7 @@ export default class Utils {
_.find(years, { year: year }).count++;
}
}
}
years.sort((a, b) => parseFloat(b.year) - parseFloat(a.year));
for (let index = 0; index < years.length; index++) {
let item = years[index];

View file

@ -1,24 +1,34 @@
import Book from '../../data/Book';
import Settings, { SETTINGS_FILE } from '../../data/Settings';
const express = require('express');
const moment = require('moment');
const router = express.Router();
const config = require('../../../site/settings.json');
//const config = require('../../../site/settings.json');
const book = new Book();
const settings = new Settings();
const indexLimit = 5;
//--------------------------
// Index
//--------------------------
router.get('/', function (req, res) {
settings
.load(SETTINGS_FILE)
.then(config => {
book.getPage().then(result => {
result.sort((a, b) => parseFloat(b.metadata.id) - parseFloat(a.metadata.id));
let indexPages = [];
let indexCount = 0;
result.forEach(page => {
if (typeof page.metadata.deleted === 'undefined' || page.metadata.deleted === false) {
if (
typeof page.metadata.deleted === 'undefined' ||
page.metadata.deleted === false
) {
if (indexCount === indexLimit) return;
indexPages.push({ page: page, date: moment(page.metadata.created).fromNow() });
indexPages.push({
page: page,
date: moment(page.metadata.created).fromNow()
});
++indexCount;
}
});
@ -31,6 +41,15 @@ router.get('/', function (req, res) {
res.render('index', pageData);
});
})
.catch(err => {
if (err.code === 'ENOENT') {
let setupData = { title: 'Fipamo Set up' };
res.render('init', setupData);
} else {
//render error page
}
});
});
//--------------------------

View file

@ -1,14 +1,19 @@
import Book from '../../../brain/data/Book';
import Settings, { SETTINGS_FILE } from '../../data/Settings';
const express = require('express');
const router = express.Router();
const _ = require('lodash');
const settings = require('../../../site/settings.json');
//const settings = require('../../../site/settings.json');
const book = new Book();
const settings = new Settings();
//--------------------------
// SETTINGS
//--------------------------
router.get('/', function (req, res) {
if (req.session.user) {
settings
.load(SETTINGS_FILE)
.then(settings => {
var nav = [];
book.getPage()
.then(pages => {
@ -64,6 +69,10 @@ router.get('/', function (req, res) {
//console.log('ERROR', err);
//render error pages
});
})
.catch(err => {
console.log('ERROR', err);
});
} else {
res.redirect('/@/dashboard');
}

View file

@ -92,6 +92,9 @@ router.get('/list/:filter?/:page?', function (req, res) {
router.get('/add/new', function (req, res) {
if (req.session.user) {
//need to grab a few copy of settings for the lastest index
fs.ensureDir(
'./public/assets/images/blog/' + moment().format('YYYY') + '/' + moment().format('MM'),
() => {
fs.readJSON('site/settings.json')
.then(config => {
res.render('page-edit', {
@ -108,6 +111,8 @@ router.get('/add/new', function (req, res) {
});
})
.catch(err => {});
}
);
} else {
res.redirect('/@/dashboard');
}

View file

@ -1,18 +1,21 @@
import Settings, { SETTINGS_FILE } from '../../data/Settings';
const express = require('express');
const router = express.Router();
const FileHound = require('filehound');
const fs = require('fs-extra');
var settings = [];
const settings = new Settings();
var config = [];
//--------------------------
// SETTINGS
//--------------------------
router.get('/', function (req, res) {
fs.readJson('site/settings.json')
settings
.load(SETTINGS_FILE)
.then(obj => {
settings = obj;
config = obj;
})
.catch(() => {
//console.error(err);
.catch(err => {
console.log('SETTINGS ERROR', err);
});
loadThemes().then(themes => {
if (req.session.user) {
@ -34,7 +37,7 @@ router.get('/', function (req, res) {
welcome: 'Your Settings',
status: true,
themes: themes,
settings: settings,
settings: config,
member: memberInfo[0]
});
} else {
@ -46,6 +49,9 @@ module.exports = router;
function loadThemes() {
return new Promise((resolve, reject) => {
settings
.load(SETTINGS_FILE)
.then(settings => {
FileHound.create()
.paths('content/themes')
.ext('json')
@ -74,5 +80,9 @@ function loadThemes() {
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
});
}

31
brain/views/init.pug Normal file
View file

@ -0,0 +1,31 @@
extends frame
block main-content
#dash-index
#dash-index-wrapper
.dash-init#dash-init
h1 HI! Let's get you set up, champ.
h2 Just a few questions to get started
br
form#init-form
label What's your handle?
br
input(type='text', name='new_member_handle' id='new_member_handle', placeholder="What\'s your handle?")
br
label Let's get that email
br
input(type='text', name='new_member_email' id='new_member_email', placeholder="Email Please")
br
label Let's get a password
br
input(type='password', name='new_member_pass' id='new_member_pass', placeholder="Password Please")
br
label And let's confirm that password
br
input(type='password', name='new_member_pass2' id='new_member_pass2', placeholder="Email Confirm")
br
label And finally, a title
br
input(type='text', name='new_member_title' id='new_member_title', placeholder="Site Title Please")
br
button#init-blog(data-action='blog-init' type='submit') LETSGO

6
package-lock.json generated
View file

@ -5412,6 +5412,12 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prettier": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
"dev": true
},
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",

View file

@ -60,6 +60,7 @@
"animejs": "^3.2.0",
"babel-preset-env": "^1.7.0",
"bulma.styl": "^0.6.11",
"prettier": "^2.0.5",
"scramble-text": "0.0.8",
"stylus": "^0.54.7"
}

View file

@ -0,0 +1,14 @@
[
{
"id": "",
"handle": "",
"avi": "/assets/images/global/default-avi.png",
"email": "",
"password": "",
"key": "",
"role": "",
"created": "",
"updated": "",
"deleted": null
}
]

View file

@ -0,0 +1,26 @@
{
"global": {
"base_url": "http://your.domain",
"title": "This is a Title",
"descriptions": "Because it should be easy.",
"background": "/assets/images/global/default-bg.jpg",
"private": "true",
"renderOnSave": "false",
"theme": "default-light"
},
"library_stats": {
"current_index": 1
},
"email": {
"smtp": {
"domain": "",
"email": "",
"password": ""
},
"mailgun": {
"domain": "",
"api-key": ""
}
},
"menu": []
}

View file

@ -1,22 +0,0 @@
{
"url": "http://your.domain",
"title": "This is the title",
"description": "A few words describing the site",
"theme": "default-light",
"private": "false",
"feautred-url":"fancybackground.something",
"email":
{
"smtp":
{
"domain": "",
"email": "",
"password": ""
},
"mailgun":
{
"domain": "",
"api-key": ""
}
}
}

View file

@ -1,4 +1,9 @@
import FipamoApi, { REQUEST_TYPE_POST, CONTENT_TYPE_JSON, API_LOGIN } from '../libraries/FipamoAPI';
import FipamoApi, {
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
API_LOGIN,
API_INIT
} from '../libraries/FipamoAPI';
import DataUitls from './utils/DataUtils';
import * as DataEvent from './events/DataEvent';
import DashManager from './controllers/DashManager';
@ -20,10 +25,16 @@ export default class Base {
// methods
//--------------------------
start() {
if (document.getElementById('dash-form') || document.getElementById('dash-init')) {
if (document.getElementById('dash-form')) {
document
.getElementById('login-btn')
.addEventListener('click', e => this.handleLogin(e));
} else {
document
.getElementById('init-blog')
.addEventListener('click', e => this.handleSetup(e));
}
} else {
new DashManager();
}
@ -57,4 +68,25 @@ export default class Base {
//console.log(err);
});
}
handleSetup(e) {
e.stopPropagation();
e.preventDefault();
let setUpForm = data.formDataToJSON(document.getElementById('init-form'));
api.request(API_INIT, DataEvent.API_INIT, REQUEST_TYPE_POST, CONTENT_TYPE_JSON, setUpForm)
.then(r => {
let response = JSON.parse(r.request['response']);
if (response.type === DataEvent.API_INIT_LAME) {
notify.alert(response.message, false);
} else {
notify.alert(response.message, true);
setTimeout(() => {
//window.location = '/@/dashboard';
}, 500);
}
})
.catch(err => {
//console.log(err);
});
}
}

View file

@ -29,6 +29,9 @@ export const API_PAGE_DELETE = 'erasingPage';
export const API_SETTINGS_WRITE = 'savingSettings';
export const API_IMAGES_UPLOAD = 'uploadProfileImages';
export const API_RENDER_PAGES = 'renderPages';
export const API_INIT = 'blogInit';
export const API_INIT_GOOD = 'blogInitGood';
export const API_INIT_LAME = 'blogInitLame';
class DataEvent {
//--------------------------
// methods

View file

@ -5,6 +5,7 @@ export const REQUEST_TYPE_DELETE = 'DELETE';
export const CONTENT_TYPE_JSON = 'json';
export const CONTENT_TYPE_FORM = 'x-www-form-urlencoded';
export const API_STATUS = '/api/v1/auth/status';
export const API_INIT = '/api/v1/auth/init';
export const API_LOGIN = '/api/v1/auth/login';
export const API_GET_NAV = '/api/settings/nav';
export const API_NEW_PAGE = '/api/v1/page/write/new';

View file

@ -14,6 +14,23 @@
height 100%
margin 0 auto
.dash-init
width 100%
max-width 900px
margin 0 auto
color $secondary
label
color $primary
form
background $white
padding 10px
input[type=email], input[type=password], input[type=text]
margin-bottom 15px
button
height 30px
width 100px
#dash-login
width 100%
max-width 900px