diff --git a/.babelrc b/.babelrc
index 744c7d6..684fff6 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,13 +1 @@
-{
- "presets": [],
- "plugins": [
- [
- "prismjs",
- {
- "languages": ["html", "markdown", "markup"],
- "theme": "okaidia",
- "css": false
- }
- ]
- ]
-}
+{ "presets": ["env"] }
diff --git a/.eslintrc b/.eslintrc
index 3c174b3..a1f75dd 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,70 +1,70 @@
{
- "parserOptions": {
- "ecmaVersion": 7,
- "sourceType": "module",
- "ecmaFeatures": {}
- },
- "rules": {
- "constructor-super": 2,
- "for-direction": 2,
- "getter-return": 2,
- "no-case-declarations": 2,
- "no-class-assign": 2,
- "no-compare-neg-zero": 2,
- "no-cond-assign": 2,
- "no-console": 1,
- "no-const-assign": 2,
- "no-constant-condition": 2,
- "no-control-regex": 1,
- "no-debugger": 2,
- "no-delete-var": 2,
- "no-dupe-args": 2,
- "no-dupe-class-members": 2,
- "no-dupe-keys": 2,
- "no-duplicate-case": 2,
- "no-empty": 2,
- "no-empty-character-class": 2,
- "no-empty-pattern": 2,
- "no-ex-assign": 2,
- "no-extra-boolean-cast": 2,
- "no-extra-semi": 2,
- "no-fallthrough": 2,
- "no-func-assign": 2,
- "no-global-assign": 2,
- "no-inner-declarations": 2,
- "no-invalid-regexp": 2,
- "no-irregular-whitespace": 2,
- "no-mixed-spaces-and-tabs": 2,
- "no-new-symbol": 2,
- "no-obj-calls": 2,
- "no-octal": 2,
- "no-redeclare": 2,
- "no-regex-spaces": 2,
- "no-self-assign": 2,
- "no-sparse-arrays": 2,
- "no-this-before-super": 2,
- "no-undef": 2,
- "no-unexpected-multiline": 2,
- "no-unreachable": 2,
- "no-unsafe-finally": 2,
- "no-unsafe-negation": 2,
- "no-unused-labels": 2,
- "no-unused-vars": 1,
- "no-useless-escape": 1,
- "require-yield": 2,
- "use-isnan": 2,
- "valid-typeof": 2,
- "no-duplicate-imports": 2
- },
- "env": {
- "node": true,
- "browser": true,
- "es6": true
- },
- "globals": {
- "_": false,
- "hljs": false,
- "Sortable": false,
- "Prism": false
- }
-}
+ "parserOptions": {
+ "ecmaVersion": 7,
+ "sourceType": "module",
+ "ecmaFeatures": {}
+ },
+ "rules": {
+ "constructor-super": 2,
+ "for-direction": 2,
+ "getter-return": 2,
+ "no-case-declarations": 2,
+ "no-class-assign": 2,
+ "no-compare-neg-zero": 2,
+ "no-cond-assign": 2,
+ "no-console": 1,
+ "no-const-assign": 2,
+ "no-constant-condition": 2,
+ "no-control-regex": 1,
+ "no-debugger": 2,
+ "no-delete-var": 2,
+ "no-dupe-args": 2,
+ "no-dupe-class-members": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-empty": 2,
+ "no-empty-character-class": 2,
+ "no-empty-pattern": 2,
+ "no-ex-assign": 2,
+ "no-extra-boolean-cast": 2,
+ "no-extra-semi": 2,
+ "no-fallthrough": 2,
+ "no-func-assign": 2,
+ "no-global-assign": 2,
+ "no-inner-declarations": 2,
+ "no-invalid-regexp": 2,
+ "no-irregular-whitespace": 2,
+ "no-mixed-spaces-and-tabs": 2,
+ "no-new-symbol": 2,
+ "no-obj-calls": 2,
+ "no-octal": 2,
+ "no-redeclare": 2,
+ "no-regex-spaces": 2,
+ "no-self-assign": 2,
+ "no-sparse-arrays": 2,
+ "no-this-before-super": 2,
+ "no-undef": 2,
+ "no-unexpected-multiline": 2,
+ "no-unreachable": 2,
+ "no-unsafe-finally": 2,
+ "no-unsafe-negation": 2,
+ "no-unused-labels": 2,
+ "no-unused-vars": 2,
+ "no-useless-escape": 1,
+ "require-yield": 2,
+ "use-isnan": 2,
+ "valid-typeof": 2,
+ "no-duplicate-imports": 2
+ },
+ "env": {
+ "node": true,
+ "browser": true,
+ "es6": true
+ },
+ "globals": {
+ "_": false,
+ "hljs": false,
+ "Sortable": false,
+ "Prism": false
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a7bce2e..af2a7fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,42 +1,25 @@
node_modules/
-src/node_modules/
-.parcel-cache/
.sass-cache/
.cache/
.nova/
+/*
!public/
public/*
!public/favicon.ico
-!public/index.php
!public/assets
public/assets/*
-!public/assets/css
-public/assets/css/*
-!public/assets/css/dash.css
-!public/assets/scripts
-public/assets/scripts/*
-!public/assets/scripts/Start.js
!public/assets/images
public/assets/images/*
!public/assets/images/global/
!public/assets/images/global/*
-!content/
-content/*
-!content/themes
-content/themes/*
-!content/themes/fipamo-default
-!content/themes/fipamo-default/*
-vendor/
-cache/
-_temp
+content/
.ftpconfig
.vscode/
*.swp
-config/settings.json
-config/folks.json
-config/pages.json
-config/tags.json
+site/settings.json
+site/folks.json
+site/pages.json
+site/tags.json
+site/_backup
*.DS_Store
-config.codekit3
-/config/backups
\ No newline at end of file
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
deleted file mode 100644
index cd8588c..0000000
--- a/.php-cs-fixer.php
+++ /dev/null
@@ -1,74 +0,0 @@
-setRiskyAllowed(true)
- ->setRules([
- '@PSR12' => true,
- 'array_indentation' => true,
- 'array_syntax' => [
- 'syntax' => 'short',
- ],
- 'combine_consecutive_unsets' => true,
- 'method_chaining_indentation' => true,
- 'class_attributes_separation' => [
- 'elements' => [
- 'const' => 'none',
- 'method' => 'one',
- 'property' => 'none',
- 'trait_import' => 'none',
- ],
- ],
- 'multiline_whitespace_before_semicolons' => [
- 'strategy' => 'no_multi_line',
- ],
- 'single_quote' => false,
-
- 'binary_operator_spaces' => [
- 'default' => 'single_space',
- 'operators' => [
- '=' => 'align_single_space_minimal',
- '=>' => 'align_single_space_minimal',
- ],
- ],
- 'braces' => [
- 'allow_single_line_closure' => true,
- ],
- 'concat_space' => [
- 'spacing' => 'one',
- ],
- 'declare_equal_normalize' => true,
- 'function_typehint_space' => true,
- 'single_line_comment_style' => [
- 'comment_types' => [
- 'hash',
- ],
- ],
- 'include' => true,
- 'lowercase_cast' => true,
- 'no_extra_blank_lines' => [
- 'tokens' => [
- 'curly_brace_block',
- 'extra',
- 'parenthesis_brace_block',
- 'throw',
- ]
- ],
- 'no_multiline_whitespace_around_double_arrow' => true,
- 'no_spaces_around_offset' => true,
- 'no_unused_imports' => true,
- 'no_whitespace_before_comma_in_array' => true,
- 'no_whitespace_in_blank_line' => true,
- 'object_operator_without_whitespace' => true,
- 'single_blank_line_before_namespace' => true,
- 'ternary_operator_spaces' => true,
- 'trim_array_spaces' => true,
- 'unary_operator_spaces' => true,
- 'whitespace_after_comma_in_array' => true,
- 'single_line_after_imports' => true,
- 'ordered_imports' => [
- 'sort_algorithm' => 'none',
- ],
- //Other rules here...
- ])
- ->setLineEnding("\n");
diff --git a/.prettierignore b/.prettierignore
index f967116..c673d93 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,6 @@
.babelrc
README.md
-*.twig
-*.sass
+*.pug
+*.styl
*.json
-*.php
-*.md
+
diff --git a/.prettierrc b/.prettierrc
index 57b161e..8b8bad9 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,36 +1,17 @@
{
- "overrides": [
- {
- "files": ".prettierrc",
- "options": { "parser": "json" }
- },
- {
- "files": "*.scss",
- "options": {
- "tabWidth": 4,
- "semi": false,
- "singleQuote": true,
- "printWidth": 90
- }
- },
- {
- "files": "*.js",
- "options": {
- "arrowParens": "avoid",
- "bracketSpacing": true,
- "htmlWhitespaceSensitivity": "css",
- "insertPragma": false,
- "bracketSameLine": false,
- "jsxSingleQuote": true,
- "proseWrap": "preserve",
- "requirePragma": false,
- "semi": true,
- "singleQuote": true,
- "trailingComma": "none",
- "useTabs": true,
- "tabWidth": 4,
- "printWidth": 90
- }
- }
- ]
-}
+ "arrowParens": "avoid",
+ "bracketSpacing": true,
+ "htmlWhitespaceSensitivity": "css",
+ "insertPragma": false,
+ "jsxBracketSameLine": false,
+ "jsxSingleQuote": true,
+ "parser": "babel",
+ "proseWrap": "preserve",
+ "requirePragma": false,
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "useTabs": true,
+ "tabWidth": 4,
+ "printWidth": 100
+}
\ No newline at end of file
diff --git a/.stylelintrc b/.stylelintrc
deleted file mode 100644
index 4448120..0000000
--- a/.stylelintrc
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "extends": [
- "stylelint-config-standard-scss",
- "stylelint-config-prettier-scss"
- ]
-}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a177288
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM node:latest
+
+COPY . /opt/fipamo
+WORKDIR /opt/fipamo
+
+RUN npm install pm2 -g \
+ && npm install .
+
+EXPOSE 2314
+
+VOLUME /opt/fipamo/public
+VOLUME /opt/fipamo/content
+VOLUME /opt/fipamo/site
+VOLUME /opt/fipamo/config.json
+VOLUME /opt/fipamo/site-settings.json
+
+ENTRYPOINT ["/usr/local/bin/npm"]
+CMD ["start", "--", "--no-daemon"]
diff --git a/README.md b/README.md
index b554f92..d27f668 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,12 @@
+![This is Fipamo](https://playvicio.us/base-assets/images/fipamo-brand.png)
# Fipamo means to save
-The Fipamo project was born from a need for a simple, easy to use no data blog platform that doesn't require much effort to set up and maintain. Fipamo uses Markdown to handle posts and renders them to flat html so you can serve them from anywhere. No complicated set ups. No long list of dependencies. Just set up and go.
+The Fipamo project was born from a need for a simple, easy to use no data blog platform that doesn't require much effort to set up and maintain. Fipamo uses Markdown to handle posts and renders them to flat html so you can serve them from anywhere. No complicated set ups. No long list of dependecies. Just write and publish.
Because nobody has time for all that.
-## Check the (WIP) Docs to get you started.
+## So here's what you need to use Fipamo
+You'll need at least `Node 10.16.0` and `PM2` is you want to use Fipamo to serve your html, css and js files. If you want to contribute to the project, you're gonna need `nodemon` for you dev environment.
+
+Once you have all of that taken care of, run `npm install` and go to `https://yourdomain/@/dashboard` start your set up. Yup. That's it.
-[Getting Started](https://koodu.ubiqueros.com/are0h/Fipamo/wiki/00---Start)
diff --git a/brain/_loader.php b/brain/_loader.php
deleted file mode 100644
index 87a4cf9..0000000
--- a/brain/_loader.php
+++ /dev/null
@@ -1,10 +0,0 @@
- 'Authorized',
- 'type' => 'apiUseAuthorized',
- 'token' => Session::get('token'),
- ];
- } else {
- $result = [
- 'message' => 'Not Authorized',
- 'type' => 'apiUseNotAuthorized',
- ];
- }
- return $result;
- }
-
- public static function login($body)
- {
- $result = [];
- switch (Auth::login($body)) {
- case 'no_name':
- $result = [
- 'message' => 'Need to see some id, champ',
- 'type' => 'requestLame',
- ];
- break;
- case 'bad_pass':
- $result = [
- 'message' => 'Check your password, sport',
- 'type' => 'requestLame',
- ];
- break;
- default:
- $result = [
- 'message' => 'Welcome back',
- 'type' => 'requestGood',
- ];
- break;
- }
-
- return $result;
- }
-
- public static function logout($body)
- {
- Auth::logout($body);
- $result = [
- 'message' => 'Till next time, g.',
- 'type' => 'TASK_LOGOUT',
- ];
- return $result;
- }
-
- public static function requestSecret($body)
- {
- $result = Auth::findSecret($body);
- return $result;
- }
-
- public static function resetPassword($body)
- {
- $result = Auth::makeNewPassword($body);
- return $result;
- }
-}
diff --git a/brain/api/v1/ImagesAPI.php b/brain/api/v1/ImagesAPI.php
deleted file mode 100644
index 6d303ea..0000000
--- a/brain/api/v1/ImagesAPI.php
+++ /dev/null
@@ -1,79 +0,0 @@
-getUploadedFiles();
- $uploadPath = '';
- $path = date('Y') . '/' . date('m');
- $response = [];
- switch ($type) {
- case 'avatar':
- $image = $file['avatar_upload'];
- $uploadPath = '../public/assets/images/user/' . $path;
- break;
- case 'background':
- $image = $file['background_upload'];
- $uploadPath = '../public/assets/images/user/' . $path;
- break;
- default:
- $image = $file['post_image'];
- $path = date('Y') . '/' . date('m');
- $uploadPath = '../public/assets/images/blog/' . $path;
- break;
- }
-
- $result = FileUploader::uploadFile($uploadPath, $image);
-
- switch ($type) {
- case 'avatar':
- $response = [
- 'message' => 'Avatar Added. You look great!',
- 'type' => 'avatarUploaded',
- 'url' => '/assets/images/user/' . $path . '/' . $image->getClientFileName(),
- ];
-
- //update member data
- Member::updateData(
- 'avi',
- '/assets/images/user/' . $path . '/' . $image->getClientFileName()
- );
-
- break;
- case 'background':
- $response = [
- 'message' => "Background plugged in. That's nice!",
- 'type' => 'siteBackgroundUploaded',
- 'url' => '/assets/images/user/' . $path . '/' . $image->getClientFileName(),
- ];
-
- //update settings file
- Settings::updateGlobalData(
- 'background',
- '/assets/images/user/' . $path . '/' . $image->getClientFileName()
- );
-
- break;
- default:
- $response = [
- 'message' => 'Image Added. Very slick',
- 'type' => 'postImageAdded',
- 'url' => '/assets/images/blog/' . $path . '/' . $image->getClientFileName(),
- ];
- break;
- }
-
- return $response;
- }
-}
diff --git a/brain/api/v1/InitAPI.php b/brain/api/v1/InitAPI.php
deleted file mode 100644
index a363fc9..0000000
--- a/brain/api/v1/InitAPI.php
+++ /dev/null
@@ -1,34 +0,0 @@
- 'blogInitFail', 'message' => 'Site already set up'];
- } else {
- switch ($task) {
- case 'init':
- $result = Setup::init($request);
- break;
- case 'restore':
- $result = Setup::restore($request);
- break;
- }
- }
-
- return $result;
- }
-}
diff --git a/brain/api/v1/MailerAPI.php b/brain/api/v1/MailerAPI.php
deleted file mode 100644
index ac6261f..0000000
--- a/brain/api/v1/MailerAPI.php
+++ /dev/null
@@ -1,32 +0,0 @@
- 'You need to be logged in for this, champ.',
- 'type' => 'MAILER_ERROR',
- ];
- }
- } else {
- }
-
- return $result;
- }
-}
diff --git a/brain/api/v1/PagesAPI.php b/brain/api/v1/PagesAPI.php
deleted file mode 100644
index fdf85dd..0000000
--- a/brain/api/v1/PagesAPI.php
+++ /dev/null
@@ -1,176 +0,0 @@
-getContents();
- $content = [];
- foreach ($pages as $page) {
- $entry = [
- 'id' => $page['id'],
- 'uuid' => $page['uuid'],
- 'title' => $page['title'],
- 'feature' => $page['feature'],
- 'path' => $page['path'],
- 'layout' => $page['layout'],
- 'tags' => $page['tags'],
- 'author' => $page['author'],
- 'created' => $page['created'],
- 'updated' => $page['updated'],
- 'deleted' => $page['deleted'],
- 'menu' => $page['menu'],
- 'featured' => $page['featured'],
- 'published' => $page['published'],
- 'slug' => $page['slug'],
- 'content' => StringTools::sanitizeContent($page['content']),
- ];
-
- array_push($content, $entry);
- }
- switch ($task) {
- case 'published':
- $published = filter($content, function ($item) {
- return $item['published'] == true && $item['deleted'] == false;
- });
-
- $result = ['pages' => $published, 'totalItems' => count($published)];
- break;
- case 'featured':
- $featured = filter($content, function ($item) {
- return $item['featured'] == true && $item['deleted'] == false;
- });
-
- $result = [
- 'pages' => $featured,
- 'totalItems' => count($featured),
- ];
- break;
- case 'menu':
- $menu = filter($content, function ($item) {
- return $item['menu'] == true && $item['deleted'] == false;
- });
-
- $result = ['pages' => $menu, 'totalItems' => count($menu)];
- break;
- case 'single':
- $uuid = $args['fifth'];
- $page = (new Book('../content/pages'))->findPageById($uuid);
-
- $entry = [
- 'id' => $page['id'],
- 'uuid' => $page['uuid'],
- 'title' => $page['title'],
- 'feature' => $page['feature'],
- 'path' => $page['path'],
- 'layout' => $page['layout'],
- 'tags' => $page['tags'],
- 'author' => $page['author'],
- 'created' => $page['created'],
- 'updated' => $page['updated'],
- 'deleted' => $page['deleted'],
- 'menu' => $page['menu'],
- 'featured' => $page['featured'],
- 'published' => $page['published'],
- 'slug' => $page['slug'],
- 'content' => StringTools::sanitizeContent($page['content']),
- ];
- $result = $entry;
- break;
- case 'tags':
- $result = Settings::getTags();
- break;
- default:
- $result = [
- 'message' => "Hm, no task. That's unfortunate",
- 'type' => 'TASK_NONE',
- ];
- break;
- }
- return $result;
- }
-
- public static function handlePageTask($request, $args)
- {
- $task = $args['fourth'];
- switch ($task) {
- case 'delete':
- case 'create':
- case 'write':
- $body = $request->getParsedBody();
- $passed = true;
- if (!isset($body['form_token'])) {
- $result = [
- 'message' => 'No form token. Not good, sport.',
- 'type' => 'TASK_FORM_AUTH',
- ];
- } else {
- if ($body['form_token'] == Session::get('form_token')) {
- $keys = [
- 'id',
- 'uuid',
- 'layout',
- 'current_title',
- 'content',
- 'title',
- 'created',
- 'slug',
- 'tags',
- 'menu',
- 'featured',
- 'published',
- 'form_token',
- 'feature_image',
- ];
-
- foreach ($body as $key => $item) {
- if (!in_array($key, $keys)) {
- //found unnecessary key, so reject submission
- $passed = false;
- }
- }
- if ($passed) {
- $result = (new Book())->editPage($task, $request);
- } else {
- $result = [
- 'message' => 'Unneccessary key found. Post not authorized, slick.',
- 'type' => 'TASK_FORM_AUTH',
- ];
- }
- } else {
- $result = [
- 'message' => 'Form token, auth failed. Uh oh.',
- 'type' => 'TASK_FORM_AUTH',
- ];
- }
- }
-
- break;
- case 'add-entry-image':
- $result = ImagesAPI::uploadImage($request);
- break;
- default:
- $result = [
- 'message' => "Hm, no task. That's unfortunate",
- 'type' => 'TASK_NONE',
- ];
- break;
- }
-
- return $result;
- }
-}
diff --git a/brain/api/v1/SettingsAPI.php b/brain/api/v1/SettingsAPI.php
deleted file mode 100644
index 974d30a..0000000
--- a/brain/api/v1/SettingsAPI.php
+++ /dev/null
@@ -1,152 +0,0 @@
-getSettings();
- $theme = $settings['global']['theme'];
- $themeConfig = json_decode(
- file_get_contents('../content/themes/' . $theme . '/theme.json'),
- true
- );
- //check to see if dynamic rendering is active
- if (
- isset($settings['global']['dynamicRender']) &&
- $settings['global']['dynamicRender'] === 'true'
- ) {
- $result = [
- 'message' => "Dynamic Render Active! You're good!",
- 'type' => 'RENDER_SUCCESS',
- ];
- } else {
- $render = new Render();
- if (isset($themeConfig['render'])) {
- if (!$themeConfig['render'] || $themeConfig['render'] === 'false') {
- $render->renderIndex();
- $result = [
- 'message' => 'Index Rendered. HAND CLAPS',
- 'type' => 'RENDER_SUCCESS',
- ];
- } else {
- $render->renderTags();
- $render->renderArchive();
- $render->renderPages();
- $result = [
- 'message' => 'Site Rendered. GOOD EFFORT',
- 'type' => 'RENDER_SUCCESS',
- ];
- }
- } else {
- // just incase the render flag is missing
- $render->renderTags();
- $render->renderArchive();
- $render->renderPages();
- $result = [
- 'message' => 'Site Rendered. GOOD EFFORT',
- 'type' => 'RENDER_SUCCESS',
- ];
- }
- }
-
- //if render flag is set and false, just render index page for one page sites
- //otherwise, render all pages according to theme template files
-
- break;
- case 'add-avatar':
- $result = ImagesAPI::uploadImage($request, 'avatar');
- break;
- case 'add-feature-background':
- $result = ImagesAPI::uploadImage($request, 'background');
- break;
- case 'sync':
- Settings::sync($body);
- $result = [
- 'message' => "Settings Synced. You're doing great!",
- 'type' => 'settingsUpdated',
- ];
- break;
- case 'nav-sync':
- Settings::navSync($body);
- $result = [
- 'message' => 'Navigation updated. Very slick!',
- 'type' => 'menuUpdated',
- ];
- break;
- default:
- $result = [
- 'message' => "Hm, no task. That's unfortunate",
- 'type' => 'TASK_NONE',
- ];
- break;
- }
-
- return $result;
- }
-
- public static function getInfo($request, $args)
- {
- $task = $args['fourth'];
- switch ($task) {
- case 'site':
- $config = new Settings();
- $settings = $config->getSettings();
- $data = [
- 'title' => $settings['global']['title'],
- 'base_url' => $settings['global']['base_url'],
- 'description' => $settings['global']['descriptions'],
- ];
- $result = [
- 'message' => 'Settings Found',
- 'type' => 'GET_SETTINGS',
- 'data' => $data,
- ];
- break;
- case 'member':
- if (Session::active()) {
- $member = $member = Session::get('member');
- $data = ['handle' => $member['handle'], 'email' => $member['email']];
- $result = [
- 'message' => 'Member Info Found',
- 'type' => 'GET_MEMBER_INFO',
- 'data' => $data,
- ];
- } else {
- $result = [
- 'message' => "Not logged in. C'mon, bruh",
- 'type' => 'TASK_NONE',
- ];
- }
- break;
- default:
- $result = [
- 'message' => 'No Settings found. Frowny Face',
- 'type' => 'TASK_NONE',
- ];
- break;
- }
- return $result;
- }
-
- public static function createBackup()
- {
- $result = Maintenance::makeBackup();
- return $result;
- }
-}
diff --git a/brain/api/v1/auth.js b/brain/api/v1/auth.js
new file mode 100644
index 0000000..b7a9c25
--- /dev/null
+++ b/brain/api/v1/auth.js
@@ -0,0 +1,233 @@
+import * as DataEvent from '../../../src/com/events/DataEvent';
+import mdparser from 'markdown-yaml-metadata-parser';
+const uuidv4 = require('uuid/v4');
+const express = require('express');
+const router = express.Router();
+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 secret_key = '58d5aeec3c604e2837aef70bc1606f35131ab8fea9731925558f5acfaa00da60';
+const moment = require('moment');
+
+/**
+ * Get Auth Status
+ */
+router.get('/', function (req, res) {
+ var token = req.headers['x-access-token'];
+ if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
+
+ jwt.verify(token, 'super-secret-string', function (err, decoded) {
+ if (err)
+ return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
+ res.status(200).send(decoded);
+ });
+});
+
+/**
+ * Get Auth Status
+ */
+router.get('/status', function (req, res) {
+ if (req.session.user) {
+ let session = req.session;
+ res.json({
+ type: DataEvent.API_REQUEST_GOOD,
+ message: 'Auth is Good',
+ token: session.hashToken
+ });
+ } else {
+ res.json({
+ type: DataEvent.API_REQUEST_LAME,
+ message: 'NOT AUTHORIZED'
+ });
+ }
+});
+/**
+ * Login Member and return token
+ */
+router.post('/login', function (req, res) {
+ fs.readJson('site/folks.json').then(folks => {
+ let found = _.find(folks, { handle: req.body.handle });
+ if (found) {
+ if (!isValidPassword(found, req.body.password)) {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: 'CHECK YOUR PASSWORD'
+ });
+ }
+
+ let token = jwt.sign({ id: found.id }, found.key, {
+ expiresIn: 86400 // expires in 24 hours
+ });
+
+ let session = req.session;
+ session.user = found;
+ session.token = token;
+ session.hashToken = hashToken(token);
+ res.json({
+ type: DataEvent.REQUEST_GOOD,
+ message: 'Welcome Back',
+ token: session.hashToken
+ });
+ } else {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: 'Need to see some id, champ.'
+ });
+ }
+ });
+});
+
+/**
+ * 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: [] });
+
+ //set up index file as first page
+
+ fs.readFile('site/init/index-template.md', { encoding: 'utf8' }).then(file => {
+ let index = mdparser(file);
+ let data = index.metadata;
+ data.uuid = uuidv4();
+ data.path = moment().format('YYYY') + '/' + moment().format('MM');
+ data.author = body.new_member_handle;
+ data.created = moment(Date.now()).format();
+ data.updated = moment(Date.now()).format();
+
+ var init =
+ '---\n' +
+ 'id: ' +
+ data.id +
+ '\n' +
+ 'uuid: ' +
+ data.uuid +
+ '\n' +
+ 'title: ' +
+ data.title +
+ '\n' +
+ 'feature: ' +
+ data.feature +
+ '\n' +
+ 'path: ' +
+ moment(Date.now()).format('YYYY') +
+ '/' +
+ moment(Date.now()).format('MM') +
+ '\n' +
+ 'layout: ' +
+ 'index' +
+ '\n' +
+ 'tags: ' +
+ data.tags +
+ '\n' +
+ 'author: ' +
+ body.new_member_handle +
+ '\n' +
+ 'created: ' +
+ moment(Date.now()).format() +
+ '\n' +
+ 'updated: ' +
+ moment(Date.now()).format() +
+ '\n' +
+ 'deleted: ' +
+ 'false' +
+ '\n' +
+ 'menu: ' +
+ data.menu +
+ '\n' +
+ 'featured: ' +
+ data.featured +
+ '\n' +
+ 'published: ' +
+ data.published +
+ '\n' +
+ 'slug: ' +
+ data.slug +
+ '\n' +
+ '---\n' +
+ index.content;
+
+ fs.writeFile('content/pages/index.md', init)
+ .then(() => {
+ console.log('index file created');
+ })
+ .catch(err => {
+ console.log('ERROR', err);
+ });
+ });
+
+ res.json({
+ type: DataEvent.API_INIT_GOOD,
+ message: 'All Set Up'
+ });
+});
+
+//router.post('/logout', function(req, res) {});
+module.exports = router;
+
+function isValidPassword(user, password) {
+ return bcrypt.compareSync(password, user.password);
+}
+
+function hashToken(token) {
+ return bcrypt.hashSync(token, bcrypt.genSaltSync(10), null);
+}
diff --git a/brain/api/v1/mailer.js b/brain/api/v1/mailer.js
new file mode 100644
index 0000000..998d1c7
--- /dev/null
+++ b/brain/api/v1/mailer.js
@@ -0,0 +1,83 @@
+import Settings, { SETTINGS_FILE } from '../../data/Settings';
+import Auth from '../../data/Auth';
+var express = require('express');
+var router = express.Router();
+var nodemailer = require('nodemailer');
+var mg = require('nodemailer-mailgun-transport');
+const pug = require('pug');
+const settings = new Settings();
+const auth = new Auth();
+router.post('/', function (req, res) {
+ auth.authCheck(req)
+ .then(() => {
+ settings
+ .load(SETTINGS_FILE)
+ .then(settings => {
+ let transport = '';
+ var auth = '';
+ switch (settings.email.active) {
+ case 'option-smtp':
+ auth = {
+ host: settings.email.smtp.domain,
+ port: 587,
+ secure: false,
+ auth: {
+ type: 'login',
+ user: settings.email.smtp,
+ pass: settings.email.smtp.password
+ }
+ };
+ transport = nodemailer.createTransport(auth);
+ break;
+ case 'option-mg':
+ auth = {
+ auth: {
+ api_key: settings.email.mailgun.key,
+ domain: settings.email.mailgun.domain
+ }
+ };
+ transport = nodemailer.createTransport(mg(auth));
+ break;
+ }
+ let render = pug.compileFile('brain/views/email/base.pug');
+ let html = render({
+ title: settings.global.title,
+ header: 'a note from ' + settings.global.title,
+ content: req.body.content,
+ footer: 'powered by fipamo'
+ });
+ transport.sendMail(
+ {
+ from: 'control@playvico.us',
+ to: req.session.user.email, // An array if you have multiple recipients.
+ subject: 'Hey beautiful',
+ //You can use "html:" to send HTML email content. It's magic!
+ html: html
+ //You can use "text:" to send plain-text content. It's oldschool!
+ //text: 'Mailgun rocks, pow pow!'
+ },
+ function (err, info) {
+ if (err) {
+ res.json({
+ message: 'MAIL ERROR',
+ desc: err
+ });
+ } else {
+ //console.log(info);
+ res.json({
+ message: 'MAIL SENT',
+ desc: info
+ });
+ }
+ }
+ );
+ })
+ .catch(() => {
+ //console.error(err);
+ });
+ })
+ .catch(err => {
+ res.json(err);
+ });
+});
+module.exports = router;
diff --git a/brain/api/v1/pages.js b/brain/api/v1/pages.js
new file mode 100644
index 0000000..f74a689
--- /dev/null
+++ b/brain/api/v1/pages.js
@@ -0,0 +1,262 @@
+import Book from '../../data/Book';
+import Auth from '../../data/Auth';
+import Settings, { SETTINGS_FILE } from '../../data/Settings';
+import * as DataEvent from '../../../src/com/events/DataEvent';
+import Render from '../../data/Render';
+const express = require('express');
+const router = express.Router();
+const multer = require('multer');
+const fs = require('fs-extra');
+const moment = require('moment');
+const book = new Book();
+const auth = new Auth();
+const settings = new Settings();
+const render = new Render();
+const _ = require('lodash');
+const uploadPath =
+ './public/assets/images/blog/' + moment().format('YYYY') + '/' + moment().format('MM');
+
+var storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ fs.ensureDir(uploadPath, () => {
+ // dir has now been created, including the directory it is to be placed in
+ cb(null, uploadPath);
+ });
+ },
+ filename: function (req, file, cb) {
+ var splice = file.originalname.split(':');
+ cb(null, splice[0]);
+ }
+});
+
+var feature_upload = multer({
+ storage: storage
+}).array('feature_image');
+var post_upload = multer({
+ storage: storage
+}).array('post_image');
+
+/**
+ * Retrieves a page of a published entries
+ * @public
+ */
+router.get('/published/:pageNum?', (req, res) => {
+ //console.log('PAGE NUM', req.params.pageNum);
+ let pageNum = req.params.pageNum;
+ if (pageNum === null || pageNum === '' || !pageNum) pageNum = 1;
+ let pages = [];
+ book.getPage().then(result => {
+ result.sort((a, b) => parseFloat(b.metadata.id) - parseFloat(a.metadata.id));
+ let displayed = _.filter(result, page => {
+ return (
+ page.metadata.deleted === false &&
+ page.metadata.published === true &&
+ page.metadata.layout != 'index'
+ );
+ });
+ var pageLimit = 6;
+ var count = Math.ceil(displayed.length / pageLimit);
+ if (pageNum > count || isNaN(pageNum))
+ res.json({ type: DataEvent.REQUEST_LAME, message: "That page doesn't exist, champ." });
+ var rangeIndex = pageNum * pageLimit - pageLimit;
+
+ let meta = [];
+
+ for (let index = 0; index < pageLimit; index++) {
+ const page = displayed[index + rangeIndex];
+ try {
+ if (
+ page.metadata.id != null &&
+ page.metadata.deleted === false &&
+ page.metadata.published === true
+ ) {
+ let entry = page.metadata;
+ entry.content = page.content;
+ //console.log('ENTRY', entry);
+ pages.push({
+ page: entry,
+ displayDate: moment(page.metadata.created).fromNow()
+ });
+ }
+ } catch (e) {
+ //console.log("NO POST", e)
+ }
+ }
+ meta.push({ currentPage: pageNum, totalPages: count });
+ let data = { pages: pages, meta: meta };
+ res.json({
+ type: DataEvent.REQUEST_GOOD,
+ message: 'This is Page ' + pageNum + ' of ' + count,
+ data: data
+ });
+ });
+});
+
+/**
+ * Retrieves single entry
+ * @public
+ */
+
+router.get('/single/:id', (req, res) => {
+ let id = req.params.id;
+ if (id === null || id === '')
+ res.json({ type: DataEvent.REQUEST_LAME, message: " Nah, this isn't here." });
+ book.getPage(id)
+ .then(page => {
+ let entry = page.metadata;
+ entry.content = page.content;
+ res.json({
+ type: DataEvent.REQUEST_GOOD,
+ message: 'Found it. Here you go.',
+ data: entry
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: "This doesn't seem to be here, homie.",
+ err: err.message
+ });
+ });
+});
+
+/**
+ * Add/Update Page
+ */
+router.post('/write/:task?', feature_upload, (req, res) => {
+ auth.authCheck(req)
+ .then(() => {
+ let body = _.mapValues(req.body);
+ let feature = '';
+ let task = '';
+ req.params.task === 'new'
+ ? (task = DataEvent.API_PAGE_CREATE)
+ : (task = DataEvent.API_PAGE_WRITE);
+ if (req.files.length > 0) {
+ var path = req.files[0].path;
+ feature = '/' + path.substring(7, path.length);
+ } else {
+ var url = body.feature_image;
+ url != null || url != undefined || url != ''
+ ? (feature = url.substring(21, url.length))
+ : (feature = '');
+ }
+ body.feature = feature;
+ body.deleted = false;
+ //if title changes, get rid of a pages with old title
+ if (body.current_title !== body.slug) {
+ let path =
+ moment(body.created).format('YYYY') + '/' + moment(body.created).format('MM');
+
+ //remove html page
+ fs.unlink('public/' + path + '/' + body.current_title + '.html')
+ .then()
+ .catch(() => {
+ //console.log('HTML ERROR', err);
+ });
+
+ //remove markdown
+ fs.unlink('content/pages/' + path + '/' + body.current_title + '.md')
+ .then()
+ .catch(() => {
+ //console.log('MD ERROR', err);
+ });
+ }
+ book.editPage(body, body.page_uuid, task, req.session.user)
+ .then(result => {
+ if (result.type === DataEvent.PAGE_ADDED) {
+ settings.updatePageIndex();
+ }
+ //load all page data and render if render on save flag is set in settings file
+ getBookData()
+ .then(result => {
+ if (result.settings.global.renderOnSave === 'true') {
+ render
+ .publishAll(
+ result.pages,
+ result.settings.global.theme,
+ req.session.user.handle
+ )
+ .then(response => {
+ res.json({
+ type: response.type,
+ message: response.message
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.PAGES_NOT_RENDERED,
+ message: 'Uh oh. Pages not rendered, sport',
+ error: err
+ });
+ });
+ } else {
+ //console.log('DONT RENDER PAGES');
+ }
+ })
+ .catch(() => {
+ //console.log();
+ });
+ res.json(result);
+ })
+ .catch(err => {
+ res.json(err);
+ });
+ })
+ .catch(err => {
+ res.json(err);
+ });
+});
+
+/**
+ * Soft deletes Page
+ */
+
+router.post('/delete', (req, res) => {
+ auth.authCheck(req)
+ .then(() => {
+ book.editPage([], req.body.id, DataEvent.API_PAGE_DELETE, req.session.user)
+ .then(result => {
+ //remove item from menu in settings
+ res.json(result);
+ })
+ .catch(err => {
+ res.json(err);
+ });
+ })
+ .catch(err => {
+ res.json(err);
+ });
+});
+
+/**
+ * Uploads image from a Page content
+ */
+
+router.post('/add-post-image', post_upload, function (req, res) {
+ //console.log(req.body);
+ var image = req.files[0].path;
+ return res.json({
+ type: DataEvent.POST_IMAGE_ADDED,
+ message: 'Added Image',
+ url: '/' + image.substr(7, image.length)
+ });
+});
+
+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);
+ });
+ });
+}
diff --git a/brain/api/v1/settings.js b/brain/api/v1/settings.js
new file mode 100644
index 0000000..ee31887
--- /dev/null
+++ b/brain/api/v1/settings.js
@@ -0,0 +1,223 @@
+import * as DataEvent from '../../../src/com/events/DataEvent';
+import Auth from '../../data/Auth';
+import Render from '../../data/Render';
+import Settings, { SETTINGS_FILE, SETTINGS_FOLKS } from '../../data/Settings';
+import Navigation from '../../data/Navigation';
+import Book from '../../data/Book';
+const express = require('express');
+const router = express.Router();
+const multer = require('multer');
+const fs = require('fs-extra');
+const moment = require('moment');
+const _ = require('lodash');
+const auth = new Auth();
+const render = new Render();
+const book = new Book();
+const settings = new Settings();
+const nav = new Navigation();
+const uploadPath =
+ './public/assets/images/user/' + moment().format('YYYY') + '/' + moment().format('MM');
+
+var storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ fs.ensureDir(uploadPath, () => {
+ // dir has now been created, including the directory it is to be placed in
+ cb(null, uploadPath);
+ });
+ },
+ filename: function (req, file, cb) {
+ var splice = file.originalname.split(':');
+ cb(null, splice[0]);
+ }
+});
+var avatar_upload = multer({
+ storage: storage
+}).array('avatar_upload');
+var background_upload = multer({
+ storage: storage
+}).array('background_upload');
+//** SYNC POSTS */
+router.post('/sync', (req, res) => {
+ auth.authCheck(req)
+ .then(() => {
+ settings
+ .sync(req, res)
+ .then(() => {
+ res.json({
+ type: DataEvent.SETTINGS_UPDATED,
+ message: 'Settings Saved'
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: "Uh oh. Settings didn't take, sport"
+ });
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: err.type,
+ message: err.message
+ });
+ });
+});
+
+router.post('/nav-sync', (req, res) => {
+ auth.authCheck(req)
+ .then(() => {
+ // find removoed menu item page and set menu to false
+ book.getPage(req.body.remove).then(page => {
+ let body = page.metadata;
+ body.content = page.content;
+ body.menu = false;
+ book.editPage(body, body.uuid, DataEvent.API_PAGE_WRITE, req.session.user);
+ });
+ nav.sync(req.body)
+ .then(response => {
+ res.json({
+ type: response.type,
+ message: response.message
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: err
+ });
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: err.type,
+ message: err.message
+ });
+ });
+});
+
+router.post('/publish-pages', (req, res) => {
+ auth.authCheck(req)
+ .then(() => {
+ getBookData()
+ .then(result => {
+ render
+ .publishAll(
+ result.pages,
+ result.settings.global.theme,
+ req.session.user.handle
+ )
+ .then(response => {
+ res.json({
+ type: response.type,
+ message: response.message
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.PAGES_NOT_RENDERED,
+ message: 'Uh oh. Pages not rendered, sport',
+ error: err
+ });
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: DataEvent.PAGES_NOT_RENDERED,
+ message: 'Uh oh. Pages not rendered, sport',
+ error: err
+ });
+ });
+ })
+ .catch(err => {
+ res.json({
+ type: err.type,
+ message: err.message
+ });
+ });
+});
+
+/***
+ UPLOAD AVATAR
+*/
+
+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;
+ var path = req.files[0].path;
+ var image = path.substr(7, path.length);
+ folks[index].avi = '/' + image;
+ fs.writeJson('site/folks.json', folks);
+ user.avi = '/' + image;
+ res.json({
+ type: DataEvent.AVATAR_UPLOADED,
+ message: 'Changed avi. You look great.',
+ url: '/' + image
+ });
+ }
+ })
+ .catch(() => {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: 'Members Not found'
+ });
+ });
+ } else {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: "You're not logged in, champ"
+ });
+ }
+});
+
+/***
+ UPLOAD FEATURE BACKGROUND
+*/
+
+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.global.background = '/' + image;
+ fs.writeJson('site/settings.json', settings);
+ res.json({
+ type: DataEvent.SITE_BACKGROUND_UPLOADED,
+ message: 'Background Uploaded',
+ url: '/' + image
+ });
+ })
+ .catch(() => {
+ //console.log('ERROR', err);
+ });
+ } else {
+ res.json({
+ type: DataEvent.REQUEST_LAME,
+ message: "You're not logged in, champ"
+ });
+ }
+});
+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);
+ });
+ });
+}
diff --git a/brain/app.js b/brain/app.js
new file mode 100644
index 0000000..b4260b1
--- /dev/null
+++ b/brain/app.js
@@ -0,0 +1,92 @@
+var express = require('express');
+var path = require('path');
+//var favicon = require('serve-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var session = require('express-session');
+var MemoryStore = require('memorystore')(session);
+var flash = require('connect-flash');
+var app = express();
+// favicon stuff
+//app.use(favicon(path.join(__dirname, 'favicons', 'favicon.ico')));
+
+// view engine setup
+app.set('views', path.join(__dirname, './views'));
+app.set('view engine', 'pug');
+app.use(logger('dev'));
+
+app.use(bodyParser.json({ limit: '50mb' }));
+app.use(
+ bodyParser.urlencoded({
+ extended: false,
+ limit: '50mb'
+ })
+);
+app.use(cookieParser());
+app.use(express.static(path.join(__dirname, '../public'), { extensions: ['html'] }));
+
+app.use(
+ session({
+ store: new MemoryStore({
+ checkPeriod: 86400000 // prune expired entries every 24h
+ }),
+ secret: '1KqZ18W8KskE1iSw',
+ saveUninitialized: false,
+ resave: false,
+ cookie: {
+ maxAge: 608800000
+ }
+ })
+);
+app.use(flash());
+//sections
+//var front = require('./routes/front/index')(session);
+var dash = require('./routes/dash/index');
+var page = require('./routes/dash/pages');
+var settings = require('./routes/dash/settings');
+var nav = require('./routes/dash/nav');
+//api
+var pages = require('./api/v1/pages');
+var setting = require('./api/v1/settings');
+var mailer = require('./api/v1/mailer');
+var auth = require('./api/v1/auth');
+// API PATHS
+
+app.use('/api/v1/page', pages);
+app.use('/api/v1/settings', setting);
+app.use('/api/v1/auth', auth);
+app.use('/api/v1/mailer', mailer);
+// PAGES
+app.use('/@/dashboard', dash);
+app.use('/@/dashboard/page', page);
+app.use('/@/dashboard/settings', settings);
+app.use('/@/dashboard/navigation', nav);
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found');
+ err.status = 404;
+ next(err);
+});
+// error handlers
+// development error handler
+// will print stacktrace
+if (app.get('env') === 'development') {
+ app.use(function (err, req, res) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: err
+ });
+ });
+}
+// production error handler
+// no stacktraces leaked to user
+app.use(function (err, req, res) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: {}
+ });
+});
+module.exports = app;
diff --git a/brain/controller/APIControl.php b/brain/controller/APIControl.php
deleted file mode 100644
index 6f41555..0000000
--- a/brain/controller/APIControl.php
+++ /dev/null
@@ -1,231 +0,0 @@
- 'API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- break;
- case 'settings':
- $token = $request->getHeader('fipamo-access-token');
- //Verify token to get site info
- if (isset($token[0])) {
- if (Session::verifyToken($token[0])) {
- $result = SettingsAPI::getInfo($request, $args);
- } else {
- $result = [
- 'message' => 'Invalid token, API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- } else {
- $result = [
- 'message' => 'No token, API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- break;
- case 'files':
- if (Session::active()) {
- if ($args['third'] == 'backup') {
- $filename = '../config/backups/latest_backup.zip';
- if (file_exists($filename)) {
- header('Content-Type: application/zip');
- header(
- 'Content-Disposition: attachment; filename="' .
- basename($filename) .
- '"'
- );
- header('Content-Length: ' . filesize($filename));
-
- flush();
- // return readfile($filename);
- //readfile($filename);
- // delete file
- //unlink($filename);
- }
- }
- } else {
- $result = [
- 'message' => 'API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- // no break
- default:
- break;
- }
-
- $freshResponse = $response;
-
- if ($args['third'] == 'files') {
- $freshResponse
- ->getBody()
- ->write(file_get_contents('../config/backups/latest_back.zip'));
-
- $freshResponse->withHeader('Content-Type', 'application/zip');
- return $freshResponse->withAddedHeader(
- 'Content-Disposition',
- 'attachment; filename=latest_backup.zip'
- );
- } else {
- $response->getBody()->write(json_encode($result));
- return $response->withHeader('Content-Type', 'application/json');
- }
- }
-
- public static function post(
- ServerRequestInterface $request,
- ResponseInterface $response,
- array $args
- ): ResponseInterface {
- $contentType = $request->getHeader('Content-Type');
- switch ($contentType[0]) {
- case 'application/json':
- $body = json_decode(file_get_contents('php://input'), true);
- break;
- default:
- break;
- }
-
- switch (isset($args['third']) ? $args['third'] : 'none') {
- case 'restore': //move to 'api/auth'
- case 'init': //move to 'api/auth'
- $task = $args['third'];
- $result = InitApi::handleInitTasks(
- $task,
- $task == 'init' ? $body : $request
- );
- break;
- case 'backup': //move to 'api/auth'
- $token = $request->getHeader('fipamo-access-token');
- //Verify token for admin tasks
- $result = SettingsAPI::createBackup();
- /*
-
- if (Session::verifyToken($token[0])) {
- $result = SettingsAPI::createBackup();
- } else {
- $result = [
- "message" => "API access denied, homie",
- "type" => "API_ERROR",
- ];
- }
- */
- break;
- case 'login': //move to 'api/auth'
- //check if request is remote and if so, verify token
- if ($body['remote'] || $body['remote'] == 'true') {
- if (Member::verifyKey($body['key'])) {
- $result = AuthAPI::login($body);
- } else {
- $result = [
- 'message' => 'API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- } else {
- //request is local, so it's cool
- $result = AuthAPI::login($body);
- }
-
- break;
- case 'logout': //move to 'api/auth'
- $result = AuthAPI::logout($body);
- break;
- case 'get-secret': //move to 'api/auth'
- $result = AuthAPI::requestSecret($body);
- break;
- case 'reset-password': //move to 'api/auth'
- $result = AuthAPI::resetPassword($body);
- break;
- case 'page':
- $token = $request->getHeader('fipamo-access-token');
- //Verify token for admin tasks
- if (isset($token[0])) {
- if (Session::verifyToken($token[0])) {
- $result = PagesAPI::handlePageTask($request, $args);
- } else {
- $result = [
- 'message' => 'Invalid token, API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- } else {
- $result = [
- 'message' => 'No token, API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
-
- break;
- case 'settings':
- if (isset($body)) {
- $postBody = $body;
- } else {
- $postBody = null;
- }
- $task = $args['fourth'];
- if ($task == 'add-feature-background' || $task == 'add-avatar') {
- $result = SettingsAPI::handleSettingsTask($request, $args, $postBody);
- } else {
- $token = $request->getHeader('fipamo-access-token');
- if (Session::verifyToken($token[0])) {
- $result = SettingsAPI::handleSettingsTask(
- $request,
- $args,
- $postBody
- );
- } else {
- $result = [
- 'message' => 'API access denied, homie',
- 'type' => 'API_ERROR',
- ];
- }
- }
-
- break;
- case 'mailer':
- $result = MailerAPI::handleMail($request, $body, $response);
- break;
- default:
- $result = [
- 'message' => "Oh, nothing to do. That's unfortunate",
- 'type' => 'TASK_NONE',
- ];
- break;
- }
- $response->getBody()->write(json_encode($result));
- return $response->withHeader('Content-Type', 'application/json');
- }
-}
diff --git a/brain/controller/DashControl.php b/brain/controller/DashControl.php
deleted file mode 100644
index cf8c673..0000000
--- a/brain/controller/DashControl.php
+++ /dev/null
@@ -1,218 +0,0 @@
-getSettings();
- $themes = (new Themes())->getThemes(); // $config->getThemes();
- $template = 'dash/settings.twig';
- $member = Session::get('member');
- $form_token = Session::get('form_token');
- $updated = new Carbon($settings['global']['last_backup']);
- $pageOptions = [
- 'title' => 'Dash Settings',
- 'private' => $settings['global']['private'],
- 'renderOnSave' => $settings['global']['renderOnSave'],
- 'background' => $settings['global']['background'],
- 'member' => $member,
- 'ftoken' => $form_token,
- 'siteTitle' => $settings['global']['title'],
- 'baseUrl' => $settings['global']['base_url'],
- 'desc' => $settings['global']['descriptions'],
- 'lastBackup' => $updated->format('Y M D d'),
- 'currentTheme' => $settings['global']['theme'],
- 'themes' => $themes,
- 'apiStatus' => isset($settings['global']['externalAPI'])
- ? $settings['global']['externalAPI']
- : 'false',
- 'dynamicRenderStatus' => isset(
- $settings['global']['dynamicRender']
- )
- ? $settings['global']['dynamicRender']
- : 'false',
- 'mailOption' => $settings['email']['active'],
- 'mailConfig' => $settings['email'],
- 'status' => Session::active(),
- ];
- } else {
- header('Location: /dashboard');
- exit();
- }
-
- break;
- case 'navigation':
- if (Session::active()) {
- $config = new Settings();
- $settings = $config->getSettings();
- $template = 'dash/navigation.twig';
- $pageOptions = [
- 'title' => 'Edit Dash Navigation',
- 'status' => Session::active(),
- 'menu' => $settings['menu'],
- ];
- } else {
- header('Location: /dashboard');
- exit();
- }
- break;
- case 'pages':
- if (Session::active()) {
- $currentPage = isset($args['fourth']) ? $args['fourth'] : 1;
- $filter = isset($args['third']) ? $args['third'] : 'all';
- $data = (new Book())->getPages($currentPage, 4, $filter);
- $template = 'dash/book.twig';
- $pageOptions = [
- 'title' => 'Contents',
- 'entryCount' => $data['entryCount'],
- 'numOfPages' => $data['numOfPages'],
- 'currentPage' => $currentPage,
- 'filter' => $data['paginate']['sort'],
- 'stats' => $data['stats'],
- 'pages' => $data['pages'],
- 'paginate' => $data['paginate'],
- 'status' => Session::active(),
- ];
- } else {
- header('Location: /dashboard');
- exit();
- }
- break;
- case 'page':
- if (Session::active()) {
- $template = 'dash/page-edit.twig';
- $mode = $args['third'];
- $uuid = $args['fourth'];
-
- switch ($mode) {
- case 'edit':
- $page = (new Book())->findPageById($uuid);
- $views = [];
- if (str_contains($page['layout'], 'index')) {
- $views = (new Themes())->getCustomIndex();
- } else {
- $views = (new Themes())->getCustomViews();
- }
-
- $imageList = explode(',', $page['feature']);
- $fileList = explode(',', $page['files']);
-
- $images = [];
- $files = [];
- foreach ($imageList as $item) {
- $image = trim($item);
- if (!empty($image)) {
- array_push($images, $image);
- }
- }
-
- foreach ($fileList as $item) {
- $file = trim($item);
- if (!empty($file)) {
- array_push($files, $file);
- }
- }
-
- $pageOptions = [
- 'title' => 'Fipamo | Edit Page',
- 'page' => $page,
- 'mode' => $mode,
- 'token' => Session::get('form_token'),
- 'status' => Session::active(),
- 'images' => $images,
- 'files' => $files,
- 'views' => $views,
- ];
- break;
- case 'preview':
- $config = new Settings();
- $settings = $config->getSettings();
- $loader = new \Twig\Loader\FilesystemLoader(
- '../content/themes'
- );
- $display = new \Twig\Environment($loader, []);
-
- $book = new Book();
- $page = $book->findPageById($uuid);
- $pageOptions = Sorting::page($page);
- $preview = $settings['global']['theme'] .
- '/' .
- $page['layout'] .
- '.twig';
- $html = $display->render($preview, $pageOptions);
- $response->getBody()->write($html);
-
- return $response;
- break;
- default:
- $pageOptions = [
- 'title' => 'Fipamo | Create Page',
- 'token' => Session::get('form_token'),
- 'mode' => $mode,
- 'status' => Session::active(),
- ];
- break;
- }
- } else {
- header('Location: /dashboard');
- exit();
- }
- break;
- case 'logout':
- Session::kill();
- header('Location: /dashboard');
- exit();
- break;
- case 'reset-password':
- $template = 'dash/reset-password.twig';
- $pageOptions = [
- 'title' => 'Reset Password',
- ];
- break;
- default:
- $template = 'dash/start.twig';
- if (Session::active()) {
- $pageOptions = [
- 'title' => 'Welcome Back',
- 'status' => Session::active(),
- 'data' => (new Book())->getPages(1, 4),
- ];
- } else {
- $pageOptions = [
- 'title' => 'Welcome to Fipamo',
- 'status' => Session::active(),
- ];
- }
- break;
- }
- } else {
- $template = 'dash/init.twig';
- $pageOptions = ['title' => 'Fipamo Setup'];
- }
-
- return $view->render($response, $template, $pageOptions);
- }
-}
diff --git a/brain/controller/IndexControl.php b/brain/controller/IndexControl.php
deleted file mode 100644
index 19c0e08..0000000
--- a/brain/controller/IndexControl.php
+++ /dev/null
@@ -1,110 +0,0 @@
-getSettings();
- $view = Twig::fromRequest($request);
- //checks dynamic render flag for site render status
- if ($settings['global']['dynamicRender']) {
- if ($settings['global']['dynamicRender'] == 'true') {
- $loader = new \Twig\Loader\FilesystemLoader('../content/themes');
- $display = new \Twig\Environment($loader, []);
- $template = '';
- $pageOptions = [];
-
- $pageInfo = [
- 'keywords' => isset($settings['global']['keywords'])
- ? $settings['global']['keywords']
- : 'fipamo, blog, jamstack, php, markdown, js',
- 'description' => $settings['global']['descriptions'],
- 'image' => $settings['global']['base_url'] . $settings['global']['background'],
- 'baseURL' => $settings['global']['base_url'],
- ];
-
- if (isset($args['first'])) {
- switch ($args['first']) {
- case 'tags':
- $template = $settings['global']['theme'] . '/tags.twig';
- $tag = trim($args['second']);
- $taglist = Sorting::tags();
- $item = find($taglist, ['tag_name' => $tag]);
- $pageOptions = [
- 'title' => 'Pages Tagged as ' . $item['tag_name'],
- 'background' => $pageInfo['image'],
- 'tag_list' => $item['pages'],
- 'info' => $pageInfo,
- 'menu' => $settings['menu'],
- 'dynamicRender' => $settings['global']['dynamicRender'],
- ];
- break;
- case 'archives':
- $archive = Sorting::archive();
- $template = $settings['global']['theme'] . '/archive.twig';
- $pageOptions = [
- 'title' => 'Archive',
- 'background' => $pageInfo['image'],
- 'archives' => $archive,
- 'info' => $pageInfo,
- 'menu' => $settings['menu'],
- 'dynamicRender' => $settings['global']['dynamicRender'],
- ];
-
- break;
- default:
- //check if page is a menu item, if not render along path as usual
- $page = [];
- $book = new Book();
- if (is_numeric($args['first'])) {
- $page = $book->findPageBySlug($args['third']);
- } else {
- $page = $book->findPageBySlug($args['first']);
- }
- $template = $settings['global']['theme'] . '/' . $page['layout'] . '.twig';
- $pageOptions = Sorting::page($page);
-
- break;
- }
- } else {
- //index
- $template = $settings['global']['theme'] . '/' . $page['layout'] . '.twig';
- $book = new Book('');
- $page = $book->findPageBySlug();
- $pageOptions = Sorting::page($page);
- }
-
- $html = $display->render($template, $pageOptions);
- $response->getBody()->write($html);
- return $response;
- } else {
- //if dynamic flag is false, load up html
- $view = Twig::fromRequest($request);
- $html = file_get_contents('../public/index.html');
- $response->getBody()->write($html);
- return $response;
- }
- } else {
- //if flag is not present, default to static html
- $view = Twig::fromRequest($request);
- $html = file_get_contents('../public/index.html');
- $response->getBody()->write($html);
- return $response;
- }
- }
-}
diff --git a/brain/controller/RouteControl.php b/brain/controller/RouteControl.php
deleted file mode 100644
index 3ceaa26..0000000
--- a/brain/controller/RouteControl.php
+++ /dev/null
@@ -1,45 +0,0 @@
- {
+ let hash = req.headers['x-access-token'];
+ let response = [];
+ //check to see if user is logged in
+ if (!req.session.user) {
+ response = {
+ status: false,
+ type: DataEvent.API_REQUEST_LAME,
+ message: "You're not logged in, champ."
+ };
+ reject(response);
+ }
+
+ //Checks if token is a proper hash, if not reject
+ if (!self.isTokenValid(req.session.token, hash)) {
+ response = {
+ status: false,
+ type: DataEvent.API_REQUEST_LAME,
+ message: 'No Token Present. Auth Blocked'
+ };
+ reject(response);
+ //res.json();
+ } else {
+ var member = req.session.user;
+ jwt.verify(req.session.token, member.key, function (err, decoded) {
+ if (err) {
+ response = {
+ status: false,
+ type: DataEvent.API_REQUEST_LAME,
+ message: 'Invalid Token. Auth Blocked'
+ };
+ reject(response);
+ }
+ response = {
+ status: true,
+ type: DataEvent.API_REQUEST_GOOD,
+ message: 'Token Verified',
+ token: decoded
+ };
+ resolve(response);
+ });
+ }
+ });
+ }
+
+ /**
+ * Checks to make sure received token matches
+ * @parameter token: created token
+ * @parameter hashedToken: encrypted token
+ */
+ isTokenValid(token, hashedToken) {
+ return bCrypt.compareSync(token, hashedToken);
+ }
+ //--------------------------
+ // event handlers
+ //--------------------------
+}
diff --git a/brain/data/Auth.php b/brain/data/Auth.php
deleted file mode 100644
index 3956bfd..0000000
--- a/brain/data/Auth.php
+++ /dev/null
@@ -1,154 +0,0 @@
-secret;
- }
-
- public static function status()
- {
- $result = '';
- if (Session::active()) {
- $result = true;
- } else {
- $result = false;
- }
- return $result;
- }
-
- public static function login($who)
- {
- //grab member list
- $folks = (new Settings())->getFolks();
- $found = find($folks, ['handle' => $who['handle']]);
-
- if ($found) {
- //name is found, verify password
- if (password_verify($who['password'], $found['password'])) {
- $member = [
- 'handle' => $found['handle'],
- 'email' => $found['email'],
- 'role' => $found['role'],
- 'avatar' => $found['avi'],
- 'key' => $found['key'],
- ];
-
- $token = Token::create(
- $found['key'],
- $found['secret'],
- time() + 3600,
- 'localhost'
- ); //expires in an hour
-
- $form_token = md5(uniqid(microtime(), true));
- Session::start();
- Session::set('member', $member);
- Session::set('token', $token);
- Session::set('form_token', $form_token);
-
- $result = 'good_login';
- } else {
- $result = 'bad_pass';
- }
- } else {
- //if name is not found
- $result = 'no_name';
- }
- return $result;
- }
-
- public static function findSecret($data)
- {
- $result = [];
- $folks = (new Settings())->getFolks();
-
- if (!empty($data['email']) && filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
- $found = find($folks, ['email' => $data['email']]);
- if ($found) {
- //if email is cool, check mail relay status
- //if set up, send secret there, if not just return it
- $config = new Settings();
- $settings = $config->getSettings();
- $email = $settings['email']['active'];
- if ($email != 'option-none') {
- $data['mail_task'] = 'SEND_SECRET';
- $data['secret'] = $found['secret'];
- $result = Mailer::sendmail($data);
- } else {
- $result = [
- 'message' => 'Valid email, but no email set up!',
- 'type' => 'secretFound',
- 'secret' => $found['secret'],
- ];
- }
- } else {
- $result = [
- 'message' => 'No valid email, no goodies, pleighboi',
- 'type' => 'secretNotFound',
- ];
- }
- } else {
- $result = [
- 'message' => 'Aye, this address is not right, slick.',
- 'type' => 'secretNotFound',
- ];
- }
-
- return $result;
- }
-
- public static function makeNewPassword($data)
- {
- //check if passwordsmatch
- if ($data['newPass'] == $data['newPassConfirm']) {
- //verify secret
- $folks = (new Settings())->getFolks();
- $found = find($folks, ['secret' => $data['secret']]);
- if ($found) {
- //create new pass and secret key, then update file
- $hash = password_hash($data['newPass'], PASSWORD_DEFAULT);
- $freshSecret = StringTools::randomString(12);
- Member::updateData('password', $hash, $data['secret']);
- Member::updateData('secret', $freshSecret, $data['secret']);
- $result = [
- 'message' => 'Password Updated. Very nice!',
- 'type' => 'passCreated',
- ];
- } else {
- $result = [
- 'message' => 'Secret key is invalid. Try to retrieve it again',
- 'type' => 'passNotCreated',
- ];
- }
- } else {
- $result = [
- 'message' => "Passwords don't match. Try it again.",
- 'type' => 'passNotCreated',
- ];
- }
-
- return $result;
- }
-
- public static function logout()
- {
- Session::kill();
- }
-}
diff --git a/brain/data/Book.js b/brain/data/Book.js
new file mode 100644
index 0000000..59e6d08
--- /dev/null
+++ b/brain/data/Book.js
@@ -0,0 +1,233 @@
+import fh from 'filehound';
+import fs from 'fs-extra';
+import metadataParser from 'markdown-yaml-metadata-parser';
+import _ from 'lodash';
+import * as DataEvent from '../../src/com/events/DataEvent';
+import Navigation from './Navigation';
+import Utils from './Utils';
+const moment = require('moment');
+const nav = new Navigation();
+const utils = new Utils();
+
+/**
+ * Class for handling blog content pages
+ */
+
+export default class Book {
+ //--------------------------
+ // constructor
+ //--------------------------
+ constructor() {}
+ //--------------------------
+ // methods
+ //--------------------------
+ start() {}
+ /**
+ * Retrieves single page or pages
+ * @parameter id: optional id if requesting a single Page
+ */
+ getPage(id) {
+ return new Promise((resolve, reject) => {
+ fh.create()
+ .paths('content/pages')
+ .ext('md')
+ .find()
+ .then(files => {
+ let pages = [];
+ for (let index = 0; index < files.length; index++) {
+ fs.readFile(files[index], { encoding: 'utf8' }, (err, file) => {
+ pages.push(metadataParser(file));
+ });
+ }
+ if (id === null || id === null || id === undefined) {
+ setTimeout(() => {
+ //TODO: Duct tape solution until something better created
+ utils.organizeTags(pages);
+ utils.organizeArchive(pages);
+ resolve(pages);
+ }, 100);
+ } else {
+ setTimeout(() => {
+ //TODO: Duct tape solution until something better created
+
+ //make check against menu to see if page should be marked as menu item
+ //if it doesnt' exist in menu change, edit page to
+ let page = _.find(pages, list => {
+ return list.metadata.uuid === id;
+ });
+ resolve(page);
+ }, 100);
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ /**
+ * Edits single page based on id and task
+ * @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 /src/com/events
+ * @parameter user: object contain user information
+ */
+ editPage(body, id, task, user) {
+ return new Promise((resolve, reject) => {
+ let self = this;
+ let response = [];
+ switch (task) {
+ case DataEvent.API_PAGE_CREATE:
+ case DataEvent.API_PAGE_WRITE:
+ var layout = 'page';
+ var path = '';
+ fs.ensureDir(
+ 'content/pages/' +
+ moment(body.created).format('YYYY') +
+ '/' +
+ moment(body.created).format('MM') +
+ '/'
+ ).then(() => {
+ if (body.menu === 'true') {
+ body.path =
+ moment(body.created).format('YYYY') +
+ '/' +
+ moment(body.created).format('MM');
+ nav.editMenu(DataEvent.MENU_ADD_ITEM, body, user);
+ } else {
+ nav.editMenu(DataEvent.MENU_DELETE_ITEM, body, user);
+ }
+ if (body.layout !== 'page') layout = body.layout;
+ if (body.layout === null || body.layout === 'null') layout = 'page';
+ var pageWrite =
+ '---\n' +
+ 'id: ' +
+ body.id +
+ '\n' +
+ 'uuid: ' +
+ body.uuid +
+ '\n' +
+ 'title: ' +
+ body.title +
+ '\n' +
+ 'feature: ' +
+ body.feature +
+ '\n' +
+ 'path: ' +
+ moment(body.created).format('YYYY') +
+ '/' +
+ moment(body.created).format('MM') +
+ '\n' +
+ 'layout: ' +
+ layout +
+ '\n' +
+ 'tags: ' +
+ body.tags +
+ '\n' +
+ 'author: ' +
+ user.handle +
+ '\n' +
+ 'created: ' +
+ moment(body.created).format() +
+ '\n' +
+ 'updated: ' +
+ moment(Date.now()).format() +
+ '\n' +
+ 'deleted: ' +
+ body.deleted +
+ '\n' +
+ 'menu: ' +
+ body.menu +
+ '\n' +
+ 'featured: ' +
+ body.featured +
+ '\n' +
+ 'published: ' +
+ body.published +
+ '\n' +
+ 'slug: ' +
+ body.slug +
+ '\n' +
+ '---\n' +
+ body.content;
+ layout === 'index'
+ ? (path = 'content/pages/index.md')
+ : (path =
+ 'content/pages/' +
+ moment(body.created).format('YYYY') +
+ '/' +
+ moment(body.created).format('MM') +
+ '/' +
+ body.slug +
+ '.md');
+ fs.writeFile(path, pageWrite, err => {
+ // throws an error, you could also catch it here
+
+ if (err) {
+ response = { type: DataEvent.PAGE_ERROR, message: err };
+ reject(response);
+ }
+
+ // success case, the file was saved
+ if (task === DataEvent.API_PAGE_CREATE) {
+ // if new file, update settings index and page count
+ response = {
+ type: DataEvent.PAGE_ADDED,
+ message: 'New Page Created',
+ id: body.uuid
+ };
+ resolve(response);
+ } else {
+ response = {
+ type: DataEvent.PAGE_UPDATED,
+ message: 'Page saved. Nice Work'
+ };
+ resolve(response);
+ }
+ });
+ });
+
+ break;
+ case DataEvent.API_PAGE_DELETE:
+ this.getPage(id)
+ .then(page => {
+ let body = _.mapValues(page.metadata);
+
+ body.content = page.content;
+ body.deleted = moment(Date.now()).format();
+ body.menu = false;
+
+ self.editPage(body, body.uuid, DataEvent.API_PAGE_WRITE, user)
+ .then(() => {
+ let item = {
+ title: body.title,
+ id: body.id,
+ slug: body.slug,
+ uuid: body.uuid
+ };
+ nav.editMenu(DataEvent.MENU_DELETE_ITEM, item);
+
+ response = {
+ type: DataEvent.PAGE_DELETED,
+ message: 'Page deleted, sport',
+ data: { uuid: body.uuid }
+ };
+ resolve(response);
+ })
+ .catch(err => {
+ response = { type: DataEvent.PAGE_ERROR, message: err };
+ reject(response);
+ });
+ })
+ .catch(err => {
+ response = { type: DataEvent.PAGE_ERROR, message: err };
+ reject(response);
+ });
+ break;
+ }
+ });
+ }
+
+ //--------------------------
+ // event handlers
+ //--------------------------
+}
diff --git a/brain/data/Book.php b/brain/data/Book.php
deleted file mode 100644
index 71d8a26..0000000
--- a/brain/data/Book.php
+++ /dev/null
@@ -1,295 +0,0 @@
-getContents();
- $page = find($content, ['uuid' => $uuid]);
-
- return $page;
- }
-
- public function findPageBySlug(string $slug = null)
- {
- $content = $this->getContents();
- if (isset($slug)) {
- $page = find($content, ['slug' => $slug]);
- } else {
- $page = find($content, ['layout' => 'index']);
- }
-
- return $page;
- }
-
- public function editPage($task, $request)
- {
- $content = $this->getContents();
- if ($task == 'delete') {
- // $parsed = json_decode(file_get_contents("php://input"), true);
- // $body = find($content, ["uuid" => $parsed["id"]]);
- $body = $request->getParsedBody();
- } else {
- $body = $request->getParsedBody();
- }
-
- $page = find($content, ['uuid' => $body['uuid']]);
- $files = $request->getUploadedFiles();
-
- $member = Session::get('member');
-
- if ($task != 'create') {
- $path = date('Y', date($page['rawCreated'])) .
- '/' .
- date('m', date($page['rawCreated']));
- } else {
- $path = date('Y') . '/' . date('m');
- }
-
- $page_feature = '';
- $page_files = '';
-
- if (isset($files['page_files'])) {
- $imageList = '';
- $fileList = '';
- //var_dump($files['page_files']);
- foreach ($files['page_files'] as $file) {
- $type = $file->getClientMediaType();
- //var_dump($type);
- switch ($type) {
- case 'image/jpeg':
- case 'image/png':
- case 'image/gif':
- case 'image/svg':
- $imagesPath = '/assets/images/blog/' . $path . '/';
- $imageList = $imageList . $imagesPath . urlencode($file->getClientFileName()) . ', ';
-
- FileUploader::uploadFile(
- '../public/assets/images/blog/' . $path . '/',
- $file
- );
- break;
- case 'video/mp4':
- $videosPath = '/assets/video/blog/' . $path . '/';
- $imageList = $imageList . $videosPath . urlencode($file->getClientFileName()) . ', ';
-
- FileUploader::uploadFile(
- '../public/assets/video/blog/' . $path . '/',
- $file
- );
- break;
- case 'audio/mpeg':
- $soundPath = '/assets/sound/blog/' . $path . '/';
- $fileList = $fileList . $soundPath . urlencode($file->getClientFileName()) . ', ';
-
- FileUploader::uploadFile(
- '../public/assets/sound/blog/' . $path . '/',
- $file
- );
- break;
- case 'application/pdf':
- case 'text/plain':
- case 'text/rtf':
- $docPath = '/assets/docs/blog/' . $path . '/';
- $fileList = $fileList . $docPath . urlencode($file->getClientFileName()) . ', ';
-
- FileUploader::uploadFile(
- '../public/assets/docs/blog/' . $path . '/',
- $file
- );
- break;
- }
- }
- $page_feature = $imageList;
- $page_files = $fileList;
- } else {
- // if no files, just reset string from page object
- $page_feature = $page['feature'];
- $page_files = $page['files'];
- }
-
- if ($task == 'delete') {
- $deleted = 'true';
- $body['menu'] = 'false';
- $body['published'] = 'false';
- $body['featured'] = 'false';
- } else {
- $deleted = isset($page['deleted']) ? $page['deleted'] : 'false';
- }
-
- $created = $task != 'create' ? new Carbon($page['rawCreated']) : Carbon::now();
- $updated = Carbon::now();
-
- // grab current index from settings and update
- $id = $task != 'create' ? $body['id'] : Settings::getCurrentIndex();
- $uuid = $task != 'create' ? $body['uuid'] : StringTools::createUUID();
- // now that variables are done, set to body object and then convert to markdown to save
-
- $body['id'] = $id;
- $body['uuid'] = $uuid;
- $body['feature'] = $page_feature;
- $body['files'] = $page_files;
- $body['path'] = $path;
- $body['author'] = $member['handle'];
- $body['created'] = $created->format("Y-m-d\TH:i:sP");
- $body['updated'] = $updated->format("Y-m-d\TH:i:sP");
- $body['deleted'] = $deleted;
-
- $write = DocTools::objectToMD($body);
-
- // if layout is index, change path to file
-
- if ($body['layout'] == 'index') {
- $writePath = '../content/pages/start/index.md';
- } else {
- $writePath = '../content/pages/' . $path . '/' . $body['slug'] . '.md';
- }
-
- $status = DocTools::writePages($task, $path, $writePath, $write);
-
- if ($status) {
- $config = new Settings();
- $settings = $config->getSettings();
- $message = '';
-
- if (
- $settings['global']['renderOnSave'] == 'true' &&
- $settings['global']['dynamicRender'] == 'false'
- ) {
- $render = new Render();
- $render->renderTags();
- $render->renderArchive();
- $render->renderPages();
- $message = 'Filed edited and rendered. NOICE.';
- } else {
- $message = 'File edited. Nice work';
- }
-
- $response = [
- 'message' => $message,
- 'type' => $task == 'write' ? 'postUpdated' : 'postAdded',
- 'id' => $uuid,
- ];
-
- // TODO: When form submission is successful, make new form token
- // Session token doesn't reset on the front end, so turning this off for now
- // $form_token = md5(uniqid(microtime(), true));
- // Session::set("form_token", $form_token);
-
- // once saved, update menu
- $body['path'] = $path;
- Settings::updateMenu($body);
- Settings::updateTags();
- // if new page added, update current index in Settings file
- if ($task == 'create') {
- Settings::updateIndex();
- }
- } else {
- $response = [
- 'message' => "Uh oh. File save problem. Don't panic",
- 'type' => 'postError',
- 'id' => $uuid,
- ];
- }
-
- return $response;
- }
-
- public function getPages(int $page, int $limit, string $sort = null)
- {
- $content = $this->getContents();
-
- $published = filter($content, function ($item) {
- return $item['published'] == true && $item['deleted'] == false;
- });
- $deleted = filter($content, function ($item) {
- return $item['deleted'] == true;
- });
-
- // $all = $content;
- $all = filter($content, function ($item) {
- return $item['deleted'] == false;
- });
- $filter = isset($sort) ? $sort : 'all';
- switch ($filter) {
- case 'published':
- $filtered = $published;
- break;
- case 'deleted':
- $filtered = $deleted;
- break;
- default:
- $filtered = $all;
- break;
- }
- $numOfPages = ceil(count($filtered) / ($limit + 1));
- $folder = [];
-
- if (count($filtered) != 0) {
- if (count($filtered) < $limit) {
- $limit = count($filtered) - 1;
- }
- $range = $page * $limit - $limit;
-
- if ($range != 0) {
- $range = $range + 1;
- }
- for ($i = 0; $i <= $limit; ++$i) {
- if (isset($filtered[$i + $range])) {
- array_push($folder, $filtered[$i + $range]);
- } else {
- // chill out
- }
- }
- }
-
- $prev = $page - 1;
- if ($prev <= 0) {
- $prev = $numOfPages;
- }
-
- $next = $page + 1;
- if ($next > $numOfPages) {
- $next = 1;
- }
-
- return [
- 'pages' => $folder,
- 'numOfPages' => $numOfPages,
- 'entryCount' => count($filtered),
- 'paginate' => [
- 'sort' => $sort,
- 'nextPage' => $next,
- 'prevPage' => $prev,
- ],
- 'stats' => [
- 'all' => count($all),
- 'published' => count($published),
- 'deleted' => count($deleted),
- ],
- ];
- }
-
- public function getContents()
- {
- // test new contents data class
- // $new = (new Contents("../content/pages"))->getAll();
- $contents = (new Contents('../content/pages'))->getAll();
-
- return $contents;
- }
-}
diff --git a/brain/data/Contents.php b/brain/data/Contents.php
deleted file mode 100644
index 7df74da..0000000
--- a/brain/data/Contents.php
+++ /dev/null
@@ -1,185 +0,0 @@
-read($folder);
- }
-
- public function read($folder)
- {
- $folders = glob("$folder/*", GLOB_ONLYDIR);
- foreach ($folders as $folder) {
- //$this->files[] = $folder . "/";
- $this->read($folder);
- }
- $files = array_filter(glob("$folder/*md"), 'is_file');
- foreach ($files as $file) {
- $this->files[] = $file;
- }
- }
-
- public function getAll()
- {
- $environment = new Environment($this->config);
- $environment->addExtension(new CommonMarkCoreExtension());
-
- // Add the extension
- $environment->addExtension(new FrontMatterExtension());
-
- //Add Strikethrough rendering
- $environment->addExtension(new StrikethroughExtension());
-
- //add attributes to elements in markdown
- $environment->addExtension(new AttributesExtension());
-
- //add table rendering
- $environment->addExtension(new TableExtension());
-
- // Instantiate the converter engine and start converting some Markdown!
- $converter = new MarkdownConverter($environment);
-
- $contents = [];
- foreach ($this->files as $file) {
- //get meta and html from file
- $result = $converter->convertToHtml(file_get_contents($file));
- $meta = [];
- if ($result instanceof RenderedContentWithFrontMatter) {
- $meta = $result->getFrontMatter();
- }
-
- //get raw markdown from file
- $frontMatterExtension = new FrontMatterExtension();
- $parsed = $frontMatterExtension
- ->getFrontMatterParser()
- ->parse(file_get_contents($file));
-
- //never trust the front end. clean it up
- //add what sanitizer extensions we need manually
- $builder = new SanitizerBuilder();
- $builder->registerExtension(new BasicExtension());
- $builder->registerExtension(new IframeExtension());
- $builder->registerExtension(new ListExtension());
- //just add it straight because classname is already in use
- $builder->registerExtension(new \HtmlSanitizer\Extension\Table\TableExtension());
-
- //relative-a and relative-image
- $builder->registerExtension(
- new \HtmlSanitizer\Extension\Relative\A\AExtension()
- );
- $builder->registerExtension(
- new \HtmlSanitizer\Extension\Relative\Image\ImageExtension()
- );
-
- $detergent = [
- 'extensions' => ['basic', 'list', 'relative-a', 'relative-image', 'iframe', 'table'],
- 'tags' => [
- 'div' => [
- 'allowed_attributes' => ['class', 'title', 'id', 'style'],
- ],
- 'img' => [
- 'allowed_attributes' => ['src', 'alt', 'title', 'class'],
- ],
- 'iframe' => [
- 'allowed_attributes' => ['height', 'width', 'title', 'src'],
- ],
- ],
- ];
-
- $sanitizer = $builder->build($detergent);
-
- $scrubbed = $sanitizer->sanitize($result->getContent());
- $featureList = explode(',', $meta['feature']);
- $docs = '';
- if (isset($meta['files'])) {
- $fileList = explode(',', $meta['files']);
- $docs = $meta['files'];
- } else {
- $fileList = [];
- $docs = '';
- }
-
- $media = [];
- $files = [];
- foreach ($featureList as $file) {
- $item = trim($file);
- $ext = pathinfo($item, PATHINFO_EXTENSION);
- if ($item != null || $item != '') {
- array_push($media, ['file' => $item, 'type' => trim($ext)]);
- }
- }
-
- foreach ($fileList as $file) {
- $item = trim($file);
- $ext = pathinfo($item, PATHINFO_EXTENSION);
- if ($item != null || $item != '') {
- array_push($files, ['file' => $item, 'type' => trim($ext)]);
- }
- }
-
- //sort attributes into page object
- $page = [
- 'id' => $meta['id'],
- 'uuid' => $meta['uuid'],
- 'title' => $meta['title'],
- 'feature' => $meta['feature'],
- 'files' => $docs,
- 'path' => $meta['path'],
- 'layout' => $meta['layout'],
- 'tags' => $meta['tags'],
- 'author' => $meta['author'],
- 'created' => date('Y M D d', $meta['created']),
- 'updated' => date('Y M D d', $meta['updated']),
- 'rawCreated' => $meta['created'],
- 'rawUpdated' => $meta['updated'],
- 'createdYear' => date('Y', $meta['created']),
- 'createdMonth' => date('m', $meta['created']),
- 'deleted' => $meta['deleted'],
- 'menu' => $meta['menu'],
- 'featured' => $meta['featured'],
- 'published' => $meta['published'],
- 'slug' => $meta['slug'],
- 'filePath' => $file,
- 'content' => $parsed->getContent(),
- 'html' => $scrubbed,
- 'media' => $media,
- 'docs' => $files
- ];
- //checks for duplicates
- $uuid = $meta['uuid'];
- $found = current(
- array_filter($contents, function ($item) use ($uuid) {
- return isset($item['uuid']) && $uuid == $item['uuid'];
- })
- );
-
- // if uuid is not present, add it
- if (!$found) {
- array_push($contents, $page);
- }
- }
- $contents = orderBy($contents, ['id'], ['desc']);
- return $contents;
- }
-}
diff --git a/brain/data/Member.php b/brain/data/Member.php
deleted file mode 100644
index 31e4cd8..0000000
--- a/brain/data/Member.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getFolks();
- $found = find($folks, ['key' => $key]);
- if ($found) {
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
-
- public static function updateData(string $key, string $data, $secret = null)
- {
- $folks = (new Settings())->getFolks();
- if (isset($secret)) {
- $found = find($folks, ['secret' => $secret]);
- } else {
- $member = Session::get('member');
- $found = find($folks, ['handle' => $member['handle']]);
- }
- $found[$key] = $data;
- //record time updated
- $updated = Carbon::now();
- $found['updated'] = $updated->format("Y-m-d\TH:i:sP");
- $newFolks = [];
- array_push($newFolks, $found);
- //save updated file
- DocTools::writeSettings('../config/folks.json', $newFolks);
- //update member data in session
-
- if (!isset($secret)) {
- $member = [
- 'handle' => $found['handle'],
- 'email' => $found['email'],
- 'role' => $found['role'],
- 'avatar' => $found['avi'],
- 'key' => $found['key'],
- ];
- Session::set('member', $member);
- }
- }
-}
diff --git a/brain/data/Navigation.js b/brain/data/Navigation.js
new file mode 100644
index 0000000..a30b8d9
--- /dev/null
+++ b/brain/data/Navigation.js
@@ -0,0 +1,75 @@
+import fs from 'fs-extra';
+import _ from 'lodash';
+import * as DataEvent from '../../src/com/events/DataEvent';
+import Settings, { SETTINGS_FILE } from './Settings';
+const settings = new Settings();
+
+export default class Navigation {
+ //--------------------------
+ // constructor
+ //--------------------------
+ constructor() {}
+ //--------------------------
+ // methods
+ //--------------------------
+ sync(body) {
+ return new Promise((resolve, reject) => {
+ let response = [];
+ settings
+ .load(SETTINGS_FILE)
+ .then(settings => {
+ let payload = body;
+ settings.menu = payload.nav;
+ fs.writeJson('site/settings.json', settings)
+ .then(() => {
+ response = {
+ type: DataEvent.SETTINGS_UPDATED,
+ message: 'Menu order saved, champ'
+ };
+ resolve(response);
+ })
+ .catch(err => {
+ response = {
+ type: DataEvent.REQUEST_LAME,
+ message: err
+ };
+ reject(response);
+ });
+ })
+ .catch(err => {
+ response = {
+ type: DataEvent.REQUEST_LAME,
+ message: err
+ };
+ reject(response);
+ });
+ });
+ }
+
+ editMenu(task, item) {
+ settings.load(SETTINGS_FILE).then(settings => {
+ switch (task) {
+ case DataEvent.MENU_ADD_ITEM:
+ settings.menu.push({
+ title: item.title,
+ id: item.id,
+ slug: item.slug,
+ uuid: item.uuid,
+ path: item.path
+ });
+ break;
+ case DataEvent.MENU_DELETE_ITEM:
+ settings.menu = _.remove(settings.menu, m => {
+ return m.uuid != item.uuid;
+ });
+
+ break;
+ }
+ fs.writeJSON(SETTINGS_FILE, settings);
+ });
+ }
+
+ //--------------------------
+ // event handlers
+ //--------------------------
+}
diff --git a/brain/data/Render.js b/brain/data/Render.js
new file mode 100644
index 0000000..e49e6c9
--- /dev/null
+++ b/brain/data/Render.js
@@ -0,0 +1,289 @@
+import * as DataEvent from '../../src/com/events/DataEvent';
+import StringUtils from '../../src/com/utils/StringUtils';
+import Settings, { SETTINGS_FILE, SETTINGS_TAG } from './Settings';
+import fs from 'fs-extra';
+import sanitize from 'sanitize-html';
+import Utils from './Utils';
+const pug = require('pug');
+const md = require('markdown-it')('commonmark');
+const _ = require('lodash');
+const moment = require('moment');
+const settings = new Settings();
+
+export default class Render {
+ //--------------------------
+ // constructor
+ //--------------------------
+ constructor() {}
+ //--------------------------
+ // methods
+ //--------------------------
+ start() {}
+
+ /**
+ * Renders all pages from markdown to html
+ * @parameter pages: payload for site pages
+ * @parameter theme: current theme being used as defined in settings
+ */
+ publishAll(pages, theme, author) {
+ return new Promise((resolve, reject) => {
+ settings
+ .load(SETTINGS_FILE)
+ .then(config => {
+ let response = [];
+ let count = _.filter(pages, page => {
+ return page.metadata.deleted === false && page.metadata.published === true;
+ }).length;
+ let rendered = 0;
+ let display_count = 0;
+ let recent = [];
+ let featured = _.filter(pages, page => {
+ return (
+ page.metadata.deleted === false &&
+ page.metadata.published === true &&
+ page.metadata.featured === true
+ );
+ });
+ for (let index = 0; index < pages.length; index++) {
+ pages.sort((a, b) => parseFloat(b.metadata.id) - parseFloat(a.metadata.id));
+
+ const page = pages[index];
+ if (page.metadata.deleted === false && page.metadata.published === true) {
+ if (recent.length < config.global.display_limit) {
+ recent.push({
+ title: page.metadata.title,
+ slug: page.metadata.slug,
+ feature: page.metadata.feature,
+ created: moment(page.metadata.created).fromNow(),
+ path: page.metadata.path
+ });
+ display_count = ++display_count;
+ }
+ let writeFile, template;
+
+ let path =
+ 'public/' +
+ moment(page.metadata.created).format('YYYY') +
+ '/' +
+ moment(page.metadata.created).format('MM') +
+ '/';
+ if (page.metadata.layout === 'index') {
+ template = 'content/themes/' + theme + '/index.pug';
+ writeFile = 'public/index.html';
+ } else {
+ writeFile = path + page.metadata.slug + '.html';
+ template = 'content/themes/' + theme + '/page.pug';
+ }
+
+ let buffed = sanitize(page.content, {
+ allowedTags: ['del', 'a', 'iframe', 'img'],
+ allowedAttributes: {
+ a: ['href', 'name', 'target'],
+ img: ['src'],
+ iframe: [
+ 'height',
+ 'width',
+ 'src',
+ 'frameborder',
+ 'allow',
+ 'allowfullscreen'
+ ]
+ }
+ });
+ let bag = page.metadata.tags.split(',');
+ let tags = [];
+ for (let index = 0; index < bag.length; index++) {
+ let tag = bag[index].trim();
+ tags.push({
+ label: bag[index],
+ slug: new StringUtils().cleanString(tag)
+ });
+ }
+ buffed = new StringUtils().decodeHTML(buffed);
+
+ let html = md.render(buffed, { html: true, xhtmlOut: true });
+ let file = pug.renderFile(template, {
+ title: page.metadata.title,
+ default_bg: page.metadata.feature,
+ content: html,
+ tags: tags,
+ menu: config.menu,
+ recent_posts: recent,
+ featured_posts: featured,
+ meta: {
+ who: author,
+ when: moment(page.metadata.created).fromNow(),
+ tags: tags
+ },
+ welcome_message: page.metadata.title
+ });
+
+ fs.ensureDir(path).then(() => {
+ fs.writeFile(writeFile, file, err => {
+ // throws an error, you could also catch it here
+ if (err) {
+ response = {
+ type: DataEvent.PAGES_NOT_RENDERED,
+ message: err
+ };
+ reject(response);
+ }
+
+ // success case, the file was saved
+ });
+ });
+ rendered = ++rendered;
+ if (rendered === count) {
+ response = {
+ type: DataEvent.PAGES_RENDERED,
+ message: 'All Pages Rendered. Sweet.'
+ };
+ //utils.moveAssets();
+ new Utils().moveAssets();
+ resolve(response);
+ }
+ } else {
+ if (count === 0) {
+ response = {
+ type: DataEvent.PAGES_RENDERED,
+ message: 'No page rendering needed'
+ };
+ resolve(response);
+ }
+ //check to see if deleted pages have been renderered and delete them
+ if (page.metadata.layout !== 'index') {
+ fs.unlink(
+ 'public/' +
+ page.metadata.path +
+ '/' +
+ page.metadata.slug +
+ '.html'
+ )
+ .then()
+ .catch(() => {
+ //console.log('ERROR', err);
+ });
+ }
+ }
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ publishTags(pages) {
+ let self = this;
+ return new Promise((resolve, reject) => {
+ self.loadRenderData()
+ .then(result => {
+ let tags = result.tags.tags;
+ let renderList = [];
+ for (let index = 0; index < tags.length; index++) {
+ let tag = tags[index];
+ //console.log('**TAG**', tag.tag_name);
+ var pageList = [];
+ for (let i = 0; i < pages.length; i++) {
+ let page = pages[i];
+
+ //TODO: filter for deleted and unpublished pages
+ if (
+ page.metedata.deleted === false &&
+ page.metadata.published === true
+ ) {
+ if (_.includes(page.metadata.tags, tag.tag_name)) {
+ pageList.push({
+ title: page.metadata.title,
+ slug: page.metadata.slug
+ });
+ }
+ }
+ }
+ renderList.push({ tag: tag.tag_name, tag_list: pageList, slug: tag.slug });
+ }
+ let response = [];
+ for (let index = 0; index < renderList.length; index++) {
+ let item = renderList[index];
+ let file = pug.renderFile(
+ 'content/themes/' + result.settings.global.theme + '/tags.pug',
+ {
+ title: item.tag,
+ default_bg: result.settings.global.background,
+ content_tags: 'THESE ARE TAGS',
+ tag_list: item.tag_list,
+ menu: result.settings.menu
+ }
+ );
+ 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
+ };
+ reject(response);
+ }
+ // success case, the file was saved
+ response = {
+ type: DataEvent.TAG_PAGES_RENDERED,
+ message: 'Tag Pages ready to go. Good job.'
+ };
+ resolve(response);
+ });
+ });
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ publishArchive(archive) {
+ settings
+ .load(SETTINGS_FILE)
+ .then(settings => {
+ let file = pug.renderFile(
+ 'content/themes/' + settings.global.theme + '/archive.pug',
+ {
+ title: 'ARCHIVES',
+ default_bg: settings.global.background,
+ content_tags: 'COLD STORAGE',
+ archives: archive,
+ menu: settings.menu
+ }
+ );
+
+ fs.writeFile('public/archives.html', file, err => {
+ // throws an error, you could also catch it here
+ if (err) {
+ //console.log('ERROR', err);
+ //response = { type: DataEvent.TAG_PAGES_NOT_RENDERED, message: err };
+ }
+ // success case, the file was saved
+ });
+ })
+ .catch(() => {
+ //console.log(err);
+ });
+ }
+ loadRenderData() {
+ return new Promise((resolve, reject) => {
+ let getSettings = settings.load(SETTINGS_FILE);
+ let getTags = settings.load(SETTINGS_TAG);
+ Promise.all([getSettings, getTags])
+ .then(result => {
+ const [settings, tags] = result;
+ let data = { settings: settings, tags: tags };
+ resolve(data);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+
+ //--------------------------
+ // event handlers
+ //--------------------------
+}
diff --git a/brain/data/Render.php b/brain/data/Render.php
deleted file mode 100644
index d0be3a6..0000000
--- a/brain/data/Render.php
+++ /dev/null
@@ -1,202 +0,0 @@
-getSettings();
- $this->menu = $settings['menu'];
- $this->theme = $settings['global']['theme'];
- $this->loader = new \Twig\Loader\FilesystemLoader('../content/themes/' . $this->theme);
- $this->twig = new \Twig\Environment($this->loader, []);
- $this->pageInfo = [
- 'keywords' => isset($settings['global']['keywords'])
- ? $settings['global']['keywords']
- : 'fipamo, blog, jamstack, php, markdown, js',
- 'description' => $settings['global']['descriptions'],
- 'image' => $settings['global']['base_url'] . $settings['global']['background'],
- 'baseURL' => $settings['global']['base_url'],
- ];
- //move global theme image assets to public folder
- foreach (
- new \DirectoryIterator('../content/themes/' . $this->theme . '/assets/images/global/') as $file
- ) {
- if ($file->isDot()) {
- continue;
- }
- if (!is_file('../public/assets/images/global/' . $file->getFileName())) {
- copy(
- '../content/themes/' .
- $this->theme .
- '/assets/images/global/' .
- $file->getFileName(),
- '../public/assets/images/global/' . $file->getFileName()
- );
- } else {
- //image is already there, so chill
- }
- //print $file->getFilename() . "\n";
- }
-
- //copy current theme assets to public
-
- //clear files in css and scripts folder
- $styles = glob('../public/assets/css/*'); // get all file names
- foreach ($styles as $file) { // iterate files
- if (is_file($file)) {
- //don't erase dashboard css
- if (!$file == '../public/assets/css/dash.css') {
- unlink($file); // delete file
- }
- }
- }
-
- $scripts = glob('../public/assets/scripts/*'); // get all file names
- foreach ($scripts as $file) { // iterate files
- if (is_file($file)) {
- if (!$file == '../public/assets/scripts/Start.js') {
- unlink($file); // delete file
- }
- }
- }
- //copy theme assets to public
- $newcss = glob('../content/themes/' . $this->theme . '/assets/css/*');
- foreach ($newcss as $file) { // iterate files
- if (is_file($file)) {
- $path = explode('/', $file);
- copy($file, '../public/assets/css/' . $path[6]);
- }
- }
- $newjs = glob('../content/themes/' . $this->theme . '/assets/scripts/*');
- foreach ($newjs as $file) { // iterate files
- if (is_file($file)) {
- $path = explode('/', $file);
- copy($file, '../public/assets/scripts/' . $path[6]);
- }
- }
- }
-
- public function renderPages()
- {
- $pages = (new Book())->getContents();
- $recent = [];
- $featured = [];
- $limit = 4;
- foreach ($pages as $page) {
- $pageOptions = Sorting::page($page);
-
- $layout = $page['layout'];
- //new pages have no layout, so defautl for now
- if ($layout == '' || $layout == null) {
- $layout = 'page';
- }
-
- $template = $layout . '.twig';
- if (str_contains($page['layout'], 'index')) {
- $location = '../public/index.html';
- $dir = null;
- } else {
- // if page is a menu item, render the page on public root
- if ($page['menu'] == 'true') {
- $location = '../public/' . $page['slug'] . '.html';
- $dir = '../public/';
- } else {
- $location = '../public/' . $page['path'] . '/' . $page['slug'] . '.html';
- $dir = '../public/' . $page['path'];
- }
- }
-
- $html = $this->twig->render($template, $pageOptions);
- DocTools::writeHTML($location, $html, $dir);
- }
- }
-
- public function renderArchive()
- {
- $archive = Sorting::archive();
- $template = 'archive.twig';
- $pageOptions = [
- 'title' => 'Archive',
- 'background' => $this->pageInfo['image'],
- 'archives' => $archive,
- 'info' => $this->pageInfo,
- 'menu' => $this->menu,
- ];
-
- $html = $this->twig->render($template, $pageOptions);
- $location = '../public/archives.html';
- DocTools::writeHTML($location, $html);
- }
-
- public function renderTags()
- {
- $list = Sorting::tags();
- foreach ($list as $item) {
- $template = 'tags.twig';
- $pageOptions = [
- 'title' => 'Pages Tagged as ' . $item['tag_name'],
- 'background' => $this->pageInfo['image'],
- 'tag_list' => $item['pages'],
- 'info' => $this->pageInfo,
- 'menu' => $this->menu,
- ];
-
- $html = $this->twig->render($template, $pageOptions);
-
- $location = '../public/tags/' . $item['slug'] . '.html';
-
- //if tags folder doesn't exist, make it
- if (!is_dir('../public/tags')) {
- mkdir('../public/tags', 0755, true);
- } else {
- }
-
- if (!is_file($location)) {
- file_put_contents($location, $html);
- } else {
- ($new = fopen($location, 'w')) or die('Unable to open file!');
- fwrite($new, $html);
- fclose($new);
- }
- }
- }
-
- public function renderIndex()
- {
- //TODO: Need to fix this to account for new index templating system
- $pages = (new Book())->getContents();
- $index = find($pages, ['layout' => 'index']);
- $template = 'index.twig';
- $location = '../public/index.html';
- $dir = null;
-
- $meta = [
- 'who' => $index['author'],
- 'when' => $index['created'],
- ];
-
- $pageOptions = [
- 'title' => $index['title'],
- 'background' => $index['feature'],
- 'meta' => $meta,
- ];
-
- $html = $this->twig->render($template, $pageOptions);
- DocTools::writeHTML($location, $html, $dir);
- }
-}
diff --git a/brain/data/Session.php b/brain/data/Session.php
deleted file mode 100644
index a4dbb02..0000000
--- a/brain/data/Session.php
+++ /dev/null
@@ -1,93 +0,0 @@
- '',
- 'token' => '',
- 'form_token' => '',
- ];
-
- public static function start()
- {
- if (!is_file(self::$file)) {
- file_put_contents(self::$file, json_encode(self::$data));
- } else {
- ($new = fopen(self::$file, 'w')) or die('Unable to open file!');
- fwrite($new, json_encode(self::$data));
- fclose($new);
- }
- }
-
- public static function active()
- {
- if (!is_file(self::$file)) {
- return false;
- } else {
- $data = json_decode(file_get_contents(self::$file), true);
- if ($data['member'] != null) {
- $secret = (new Settings())->getFolks('secret');
- if ($secret == null) {
- return false;
- } else {
- if (
- Token::validate($data['token'], $secret) &&
- Token::validateExpiration($data['token'], $secret)
- ) {
- return true;
- } else {
- return false;
- }
- }
- } else {
- return false;
- }
- }
- }
-
- public static function verifyToken($token)
- {
- $data = json_decode(file_get_contents(self::$file), true);
- if ($data['member'] != null) {
- $secret = (new Settings())->getFolks('secret');
- if (
- Token::validate($token, $secret) &&
- Token::validateExpiration($token, $secret)
- ) {
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
-
- public static function set($key, $value)
- {
- $data = json_decode(file_get_contents(self::$file), true);
- $data[$key] = $value;
- ($fresh = fopen(self::$file, 'w')) or die('Unable to open file!');
- fwrite($fresh, json_encode($data));
- fclose($fresh);
- }
-
- public static function get($key)
- {
- $data = json_decode(file_get_contents(self::$file), true);
-
- return $data[$key];
- }
-
- public static function kill()
- {
- ($fresh = fopen(self::$file, 'w')) or die('Unable to open file!');
- fwrite($fresh, json_encode(self::$data));
- fclose($fresh);
- }
-}
diff --git a/brain/data/Settings.js b/brain/data/Settings.js
new file mode 100644
index 0000000..a38e608
--- /dev/null
+++ b/brain/data/Settings.js
@@ -0,0 +1,157 @@
+import * as DataEvent from '../../src/com/events/DataEvent';
+import fs from 'fs-extra';
+const _ = require('lodash');
+export const SETTINGS_FILE = 'site/settings.json';
+export const SETTINGS_FOLKS = 'site/folks.json';
+export const SETTINGS_TAG = 'site/tags.json';
+
+export default class Settings {
+ //--------------------------
+ // constructor
+ //--------------------------
+ constructor() {}
+ //--------------------------
+ // methods
+ //--------------------------
+ sync(req) {
+ let self = this;
+ return new Promise((resolve, reject) => {
+ self.loadConfigData()
+ .then(result => {
+ let payload = req.body;
+ //so payload matches loaded config
+ payload.global.display_limit = result.settings.global.display_limit;
+ payload.global.last_backup = result.settings.global.last_backup;
+ let user = req.session.user;
+ let found = _.find(result.folks, { id: user.id });
+ let needToUpdate = false;
+ let response = [];
+ if (found) {
+ let index = found.id - 1;
+ if (
+ result.folks[index].handle != payload.member.handle ||
+ result.folks[index].email != payload.member.email
+ ) {
+ user.handle = payload.member.handle;
+ user.email = payload.member.email;
+ result.folks[index].handle = payload.member.handle;
+ result.folks[index].email = payload.member.email;
+ fs.writeJson('site/folks.json', result.folks);
+ } else {
+ //no need to save
+ }
+ } else {
+ let response = {
+ type: DataEvent.REQUEST_LAME,
+ message: "You're not logged in, champ"
+ };
+ reject(response);
+ }
+ if (!_.isEqual(result.settings.global, payload.global)) {
+ let bg = payload.global.background;
+ payload.global.background = bg.substr(21, bg.length);
+ result.settings.global = payload.global;
+ needToUpdate = true;
+ } else {
+ //no need to save
+ }
+
+ if (!_.isEqual(result.settings.email, payload.email)) {
+ result.settings.email = payload.email;
+ needToUpdate = true;
+ } else {
+ //no need to save
+ }
+
+ if (needToUpdate) {
+ fs.writeJson('site/settings.json', result.settings)
+ .then(() => {
+ response = {
+ type: DataEvent.SETTINGS_UPDATED,
+ message: 'Settings Saved'
+ };
+ resolve(response);
+ })
+ .catch(() => {
+ //console.error(err);
+ });
+ } else {
+ //no need to update
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ saveTags(tags) {
+ let self = this;
+ return new Promise((resolve, reject) => {
+ self.load(SETTINGS_TAG)
+ .then(config => {
+ if (!_.isEqual(config.tags, tags)) {
+ config.tags = tags;
+ fs.writeJson('site/tags.json', config)
+ .then(() => {
+ let response = {
+ type: DataEvent.SETTINGS_UPDATED,
+ message: 'Settings Saved'
+ };
+ resolve(response);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ } else {
+ let response = {
+ type: DataEvent.SETTINGS_NOT_UPDATED,
+ message: 'Settings Already Saved'
+ };
+ resolve(response);
+ }
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+
+ updatePageIndex() {
+ fs.readJSON('site/settings.json').then(settings => {
+ settings.library_stats.current_index = ++settings.library_stats.current_index;
+ //settings.library_stats.total_pages = ++settings.library_stats.total_pages;
+ fs.writeJSON('site/settings.json', settings);
+ });
+ }
+ load(fileToLoad) {
+ return new Promise((resolve, reject) => {
+ fs.readJSON(fileToLoad)
+ .then(file => {
+ resolve(file);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+
+ loadConfigData() {
+ return new Promise((resolve, reject) => {
+ let getSettings = this.load(SETTINGS_FILE);
+ let getFolks = this.load(SETTINGS_FOLKS);
+ Promise.all([getSettings, getFolks])
+ .then(result => {
+ const [settings, folks] = result;
+ let data = { settings: settings, folks: folks };
+ resolve(data);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+
+ //--------------------------
+ // event handlers
+ //--------------------------
+}
diff --git a/brain/data/Settings.php b/brain/data/Settings.php
deleted file mode 100644
index 4b81b99..0000000
--- a/brain/data/Settings.php
+++ /dev/null
@@ -1,175 +0,0 @@
-folks = json_decode(file_get_contents('../config/folks.json'), true);
- self::$tags = json_decode(file_get_contents('../config/tags.json'), true);
- self::$settings = json_decode(
- file_get_contents('../config/settings.json'),
- true
- );
- }
-
- public static function sync($data)
- {
- $settings = self::$settings;
- $settings['global']['base_url'] = $data['global']['base_url'];
- $settings['global']['title'] = $data['global']['title'];
- $settings['global']['descriptions'] = $data['global']['descriptions'];
- $settings['global']['base_url'] = $data['global']['base_url'];
- $settings['global']['private'] = $data['global']['private'];
- $settings['global']['renderOnSave'] = $data['global']['renderOnSave'];
- $settings['global']['theme'] = $data['global']['theme'];
- $settings['global']['externalAPI'] = $data['global']['externalAPI'];
- $settings['global']['dynamicRender'] = $data['global']['dynamicRender'];
-
- Member::updateData('handle', $data['member']['handle']);
- Member::updateData('email', $data['member']['email']);
-
- $settings['email']['active'] = $data['email']['active'];
- $settings['email']['smtp'] = $data['email']['smtp'];
- $settings['email']['mailgun'] = $data['email']['mailgun'];
-
- DocTools::writeSettings('../config/settings.json', $settings);
- }
-
- public static function navSync($data)
- {
- $settings = self::$settings;
-
- $remove = $data['remove'];
- //if remove contains id, find nav item page and set menu to false
- if ($remove != null || $remove != '') {
- $page = (new Book('../content/pages'))->findPageById($remove);
- $page['menu'] = 'false';
- $page['published']
- ? ($page['published'] = 'true')
- : ($page['published'] = 'false');
- $page['featured']
- ? ($page['featured'] = 'true')
- : ($page['featured'] = 'false');
- $page['deleted']
- ? ($page['deleted'] = 'true')
- : ($page['deleted'] = 'false');
- $updated = Carbon::now();
- $created = new Carbon($page['rawCreated']);
- $page['created'] = $created->format("Y-m-d\TH:i:sP");
- $page['updated'] = $updated->format("Y-m-d\TH:i:sP");
-
- $md = DocTools::objectToMD($page);
-
- if ($page['layout'] == 'index') {
- $writePath = '../content/pages/start/index.md';
- } else {
- $writePath = '../content/pages/' . $page['path'] . '/' . $page['slug'] . '.md';
- }
-
- DocTools::writePages('write', $page['path'], $writePath, $md);
- }
-
- $settings['menu'] = [];
- $items = $data['menu'];
- foreach ($items as $item) {
- array_push($settings['menu'], [
- 'title' => $item['title'],
- 'id' => $item['id'],
- 'uuid' => $item['uuid'],
- 'slug' => $item['slug'],
- 'path' => $item['path'],
- ]);
- }
-
- DocTools::writeSettings('../config/settings.json', $settings);
- }
-
- public function getFolks($key = null)
- {
- if (isset($key)) {
- $member = Session::get('member');
- $found = find($this->folks, ['handle' => $member['handle']]);
- if ($found) {
- return $found[$key];
- }
- } else {
- return $this->folks;
- }
- }
-
- public function getSettings($key = null)
- {
- return self::$settings;
- }
-
- public static function getTags()
- {
- return self::$tags;
- }
-
- public static function updateGlobalData($key, $data)
- {
- $settings = self::$settings;
- $settings['global'][$key] = $data;
- DocTools::writeSettings('../config/settings.json', $settings);
- }
-
- public static function getCurrentIndex()
- {
- $settings = self::$settings;
- return $settings['library_stats']['current_index'];
- }
-
- public static function updateIndex()
- {
- $settings = self::$settings;
-
- $settings['library_stats']['current_index'] = $settings['library_stats']['current_index'] + 1;
-
- DocTools::writeSettings('../config/settings.json', $settings);
- }
-
- public static function updateMenu($body)
- {
- $settings = self::$settings;
- //$menu = $settings["menu"];
- $item = [
- 'title' => $body['title'],
- 'id' => $body['id'],
- 'uuid' => $body['uuid'],
- 'slug' => $body['slug'],
- 'path' => $body['path'],
- ];
- if ($body['menu'] == 'true') {
- if (!find($settings['menu'], ['uuid' => $item['uuid']])) {
- array_push($settings['menu'], $item);
- }
- } else {
- if (find($settings['menu'], ['uuid' => $item['uuid']])) {
- pull($settings['menu'], $item);
- }
- }
- DocTools::writeSettings('../config/settings.json', $settings);
- }
-
- public static function updateTags()
- {
- $tags = Sorting::tags();
- DocTools::writeSettings('../config/tags.json', $tags);
- }
-}
diff --git a/brain/data/Themes.php b/brain/data/Themes.php
deleted file mode 100644
index b82c336..0000000
--- a/brain/data/Themes.php
+++ /dev/null
@@ -1,62 +0,0 @@
-themes,
- json_decode(file_get_contents($theme . '/theme.json'), true)
- );
- }
- }
-
- public function getThemes()
- {
- return $this->themes;
- }
-
- public function getCustomIndex()
- {
- $settings = (new Settings())->getSettings();
- $currentTheme = $settings['global']['theme'];
- $folder = '../content/themes/' . $currentTheme;
- $files = array_filter(glob("$folder/*twig"), 'is_file');
- $views = [];
-
- foreach ($files as $file) {
- $path = explode('/', $file);
- $fileName = $path[4];
- if (str_contains($fileName, 'index')) {
- $page = explode('.', $fileName);
- $views[] = $page[0];
- }
- }
- return $views;
- }
-
- public function getCustomViews()
- {
- $settings = (new Settings())->getSettings();
- $currentTheme = $settings['global']['theme'];
- $folder = '../content/themes/' . $currentTheme;
- $files = array_filter(glob("$folder/*twig"), 'is_file');
- $views = [];
-
- foreach ($files as $file) {
- $path = explode('/', $file);
- $fileName = $path[4];
- if (str_contains($fileName, 'page')) {
- $page = explode('.', $fileName);
- $views[] = $page[0];
- }
- }
- return $views;
- }
-}
diff --git a/brain/data/Utils.js b/brain/data/Utils.js
new file mode 100644
index 0000000..498ff20
--- /dev/null
+++ b/brain/data/Utils.js
@@ -0,0 +1,130 @@
+import Settings, { SETTINGS_FILE } from './Settings';
+import Render from './Render';
+import StringUtils from '../../src/com/utils/StringUtils';
+import _ from 'lodash';
+const settings = new Settings();
+const render = new Render();
+const stringUtils = new StringUtils();
+const moment = require('moment');
+const fs = require('fs-extra');
+
+export default class Utils {
+ constructor() {}
+
+ /**
+ * Retrieves single page or pages
+ * @parameter pages: payload of pages
+ */
+ organizeTags(pages) {
+ let tags = [];
+ for (let index = 0; index < pages.length; index++) {
+ const page = pages[index];
+ let temp = [];
+ temp = page.metadata.tags.split(',');
+ for (let i = 0; i < temp.length; i++) {
+ let label = temp[i].trim();
+ if (!_.find(tags, { tag_name: label })) {
+ tags.push({
+ tag_name: label,
+ slug: stringUtils.cleanString(label),
+ count: 1
+ });
+ } else {
+ _.find(tags, { tag_name: label }).count++;
+ }
+ }
+ }
+ tags = _.orderBy(tags, ['tag_name'], ['asc']);
+
+ settings.saveTags(tags).then(() => {
+ render
+ .publishTags(pages)
+ .then(() => {
+ //console.log(response);
+ })
+ .catch(() => {
+ //console.log(err);
+ });
+ });
+ }
+ organizeArchive(pages) {
+ let years = [];
+ 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 });
+ } else {
+ _.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];
+ let sorted = [];
+ let filtered = _.filter(pages, page => {
+ return moment(page.metadata.created).format('YYYY') === item.year;
+ });
+ for (let index = 0; index < filtered.length; index++) {
+ let obj = filtered[index].metadata;
+ let month = moment(obj.created).format('MM');
+ if (!_.find(sorted, { month: month })) {
+ sorted.push({
+ month: month,
+ full_month: moment(obj.created).format('MMMM'),
+ count: 1,
+ pages: _.filter(pages, page => {
+ return (
+ moment(page.metadata.created).format('YYYY') === item.year &&
+ moment(page.metadata.created).format('MM') === month &&
+ page.metadata.deleted === false &&
+ page.metadata.published === true &&
+ page.metadata.layout !== 'index'
+ );
+ })
+ });
+ } else {
+ _.find(sorted, { month: month }).count++;
+ }
+ }
+ archive.push({ year: item.year, year_data: sorted });
+ }
+ render.publishArchive(archive);
+ }
+ moveAssets() {
+ settings
+ .load(SETTINGS_FILE)
+ .then(settings => {
+ //move css assets to public directory
+ fs.copy(
+ 'content/themes/' + settings.global.theme + '/assets/css',
+ 'public/assets/css',
+ function (err) {
+ if (err) {
+ //console.log('An error occured while copying the folder.', err);
+ //return console.error(err);
+ }
+ //console.log('Copy completed!');
+ }
+ );
+ //move js assets to public directory
+ fs.copy(
+ 'content/themes/' + settings.global.theme + '/assets/scripts',
+ 'public/assets/scripts',
+ function (err) {
+ if (err) {
+ //console.log('An error occured while copying the folder.', err);
+ //return console.error(err);
+ }
+ //console.log('Copy completed!');
+ }
+ );
+ })
+ .catch(() => {
+ //console.log('ERROR', err);
+ });
+ }
+}
diff --git a/brain/init/App.php b/brain/init/App.php
deleted file mode 100644
index 8b0d650..0000000
--- a/brain/init/App.php
+++ /dev/null
@@ -1,33 +0,0 @@
-add(TwigMiddleware::create($app, $twig));
- // set up routing
- $app->get(
- '/[{first}[/{second}[/{third}[/{fourth}[/{fifth}]]]]]',
- "brain\controller\RouteControl:get"
- );
- $app->post(
- '/[{first}[/{second}[/{third}[/{fourth}]]]]',
- "brain\controller\RouteControl:post"
- );
- // start the app
-
- $app->run();
- }
-}
diff --git a/brain/routes/dash/index.js b/brain/routes/dash/index.js
new file mode 100644
index 0000000..1b582c8
--- /dev/null
+++ b/brain/routes/dash/index.js
@@ -0,0 +1,63 @@
+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 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 (indexCount === indexLimit) return;
+ indexPages.push({
+ page: page,
+ date: moment(page.metadata.created).fromNow()
+ });
+ ++indexCount;
+ }
+ });
+ let pageData = [];
+ if (req.session.user) {
+ pageData = { title: config.global.title, status: true, pages: indexPages };
+ } else {
+ pageData = { title: config.global.title, status: false, pages: indexPages };
+ }
+
+ res.render('index', pageData);
+ });
+ })
+ .catch(err => {
+ if (err.code === 'ENOENT') {
+ let setupData = { title: 'Fipamo Set up' };
+ res.render('init', setupData);
+ } else {
+ res.render('error', { error: err });
+ }
+ });
+});
+
+//--------------------------
+// Logout
+//--------------------------
+router.post('/logout', function (req, res) {
+ req.logout();
+ return res.json({
+ message: 'LOGGED OUT'
+ });
+});
+module.exports = router;
diff --git a/brain/routes/dash/nav.js b/brain/routes/dash/nav.js
new file mode 100644
index 0000000..56bbbc2
--- /dev/null
+++ b/brain/routes/dash/nav.js
@@ -0,0 +1,79 @@
+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 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 => {
+ if (settings.menu.length === 0) {
+ for (let index = 0; index < pages.length; index++) {
+ let item = pages[index].metadata;
+ if (item.menu) {
+ nav.push({
+ id: item.id,
+ uuid: item.uuid,
+ title: item.title,
+ slug: item.slug,
+ path: item.path
+ });
+ }
+ }
+ } else {
+ let newpages = [];
+ nav = settings.menu;
+ for (let index = 0; index < pages.length; index++) {
+ let item = pages[index].metadata;
+ if (item.menu)
+ newpages.push({
+ id: item.id,
+ uuid: item.uuid,
+ title: item.title,
+ slug: item.slug,
+ path: item.path
+ });
+ }
+ for (let i = 0; i < newpages.length; i++) {
+ if (_.find(nav, { uuid: newpages[i].uuid })) {
+ //menu item already exists
+ } else {
+ nav.push({
+ id: newpages[i].id,
+ uuid: newpages[i].uuid,
+ title: newpages[i].title,
+ slug: newpages[i].slug,
+ path: newpages[i].path
+ });
+ }
+ }
+ }
+ res.render('navigation', {
+ menu: nav,
+ welcome: 'Edit Navigation',
+ status: true,
+ title: 'Dashboard | Navigation'
+ });
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ } else {
+ res.redirect('/@/dashboard');
+ }
+});
+module.exports = router;
diff --git a/brain/routes/dash/pages.js b/brain/routes/dash/pages.js
new file mode 100644
index 0000000..463a94a
--- /dev/null
+++ b/brain/routes/dash/pages.js
@@ -0,0 +1,172 @@
+import Book from '../../data/Book';
+const express = require('express');
+const router = express.Router();
+const moment = require('moment');
+const book = new Book();
+const uuidv4 = require('uuid/v4');
+const fs = require('fs-extra');
+//--------------------------
+// POSTS
+//--------------------------
+router.get('/list/:filter?/:page?', function (req, res) {
+ var pageNum = req.params.page;
+ var filter = req.params.filter;
+ if (pageNum == '' || pageNum == null) pageNum = 1;
+ if (filter == '' || filter == null) filter = 'all';
+ if (req.session.user) {
+ book.getPage()
+ .then(pages => {
+ pages.sort((a, b) => parseFloat(b.metadata.id) - parseFloat(a.metadata.id));
+ let all = [];
+ let deleted = [];
+ let published = [];
+ let menu = [];
+ let featured = [];
+ for (let index = 0; index < pages.length; index++) {
+ let item = pages[index].metadata;
+ if (typeof item.deleted === 'undefined' || item.deleted === false) {
+ all.push({
+ page: pages[index].metadata,
+ date: moment(pages[index].metadata.created).fromNow()
+ });
+ if (item.published == true)
+ published.push({
+ page: pages[index].metadata,
+ date: moment(pages[index].metadata.created).fromNow()
+ });
+ if (item.menu == true)
+ menu.push({
+ page: pages[index].metadata,
+ date: moment(pages[index].metadata.created).fromNow()
+ });
+ if (item.featured == true)
+ featured.push({
+ page: pages[index].metadata,
+ date: moment(pages[index].metadata.created).fromNow()
+ });
+ } else {
+ deleted.push({
+ page: pages[index].metadata,
+ date: moment(pages[index].metadata.created).fromNow()
+ });
+ }
+ }
+ var filtered;
+ switch (filter) {
+ case 'published':
+ filtered = published;
+ break;
+ case 'deleted':
+ filtered = deleted;
+ break;
+ default:
+ filtered = all;
+ break;
+ }
+ var count = Math.ceil(filtered.length / 6);
+ var pageItems = [];
+ var itemLimit = 6;
+ var rangeStart = pageNum * itemLimit - itemLimit;
+ for (var i = 0; i < itemLimit; i++) {
+ try {
+ if (filtered[i + rangeStart].page.id != null) {
+ pageItems.push({
+ page: filtered[i + rangeStart].page,
+ date: moment(filtered[i + rangeStart].page.created).fromNow()
+ });
+ }
+ } catch (e) {
+ //console.log("NO POST", e)
+ }
+ }
+ res.render('book-index', {
+ title: 'Dashbord | Book',
+ welcome: 'Your pages',
+ items: pageItems,
+ page_info: {
+ all: all.length,
+ deleted: deleted.length,
+ published: published.length,
+ pages: pages.length,
+ featured: featured.length
+ },
+ page_index: pageNum,
+ page_count: count,
+ postFilter: filter,
+ status: true
+ });
+ })
+ .then(() => {
+ //console.log(value);
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ } else {
+ res.redirect('/@/dashboard');
+ }
+});
+
+//--------------------------
+// BLOG POST ADD DISPLAY
+//--------------------------
+router.get('/add/new', function (req, res) {
+ if (req.session.user) {
+ //need to grab a few copy of settings for the lastest index
+ fs.readJSON('site/settings.json')
+ .then(config => {
+ res.render('page-edit', {
+ id: config.library_stats.current_index,
+ uuid: uuidv4(),
+ title: 'Add New Page',
+ user_status: true,
+ welcome: 'Add New Page',
+ date: moment(Date.now()).format('YYYY MMM DD'),
+ page: [],
+ rawDate: moment(Date.now()).format(),
+ status: ['false', 'false', 'false'],
+ edit: false
+ });
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ } else {
+ res.redirect('/@/dashboard');
+ }
+});
+//--------------------------
+// BLOG POST EDIT DISPLAY
+//--------------------------
+router.get('/edit/:id', function (req, res) {
+ var id = req.params.id;
+ if (req.session.user) {
+ book.getPage(id)
+ .then(page => {
+ res.render('page-edit', {
+ id: page.metadata.id,
+ uuid: page.metadata.uuid,
+ title: 'Edit Page',
+ welcome: 'Edit Page',
+ page: page.metadata,
+ date: moment(page.metadata.created).format('YYYY MMM DD HH:mm'),
+ layout: page.metadata.layout,
+ rawDate: page.metadata.created,
+ content: page.content,
+ feature: page.metadata.feature,
+ status: [
+ String(page.metadata.menu),
+ String(page.metadata.featured),
+ String(page.metadata.published)
+ ],
+ edit: true
+ });
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ } else {
+ res.redirect('/@/dashboard');
+ }
+});
+module.exports = router;
diff --git a/brain/routes/dash/settings.js b/brain/routes/dash/settings.js
new file mode 100644
index 0000000..e4fe38c
--- /dev/null
+++ b/brain/routes/dash/settings.js
@@ -0,0 +1,92 @@
+import Settings, { SETTINGS_FILE } from '../../data/Settings';
+const express = require('express');
+const router = express.Router();
+const FileHound = require('filehound');
+const fs = require('fs-extra');
+const settings = new Settings();
+var config = [];
+//--------------------------
+// SETTINGS
+//--------------------------
+router.get('/', function (req, res) {
+ settings
+ .load(SETTINGS_FILE)
+ .then(obj => {
+ config = obj;
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+ loadThemes()
+ .then(themes => {
+ if (req.session.user) {
+ let memberInfo = [];
+ let user = req.session.user;
+ memberInfo.push({
+ handle: user.handle,
+ email: user.email,
+ avi: user.avi
+ });
+ themes.sort(function (a, b) {
+ var textA = a.theme.name.toUpperCase();
+ var textB = b.theme.name.toUpperCase();
+ return textA < textB ? -1 : textA > textB ? 1 : 0;
+ });
+
+ res.render('settings', {
+ title: 'Dashboard | Settings',
+ welcome: 'Your Settings',
+ status: true,
+ themes: themes,
+ settings: config,
+ member: memberInfo[0]
+ });
+ } else {
+ res.redirect('/@/dashboard');
+ }
+ })
+ .catch(err => {
+ res.render('error', { error: err });
+ });
+});
+module.exports = router;
+
+function loadThemes() {
+ return new Promise((resolve, reject) => {
+ settings
+ .load(SETTINGS_FILE)
+ .then(settings => {
+ FileHound.create()
+ .paths('content/themes')
+ .ext('json')
+ .find()
+ .then(files => {
+ let themes = [];
+ for (let index = 0; index < files.length; index++) {
+ fs.readJSON(files[index], (err, theme) => {
+ if (theme.name == settings.global.theme) {
+ themes.push({
+ theme: theme,
+ current: 'true'
+ });
+ } else {
+ themes.push({
+ theme: theme,
+ current: 'false'
+ });
+ }
+ });
+ }
+ setTimeout(() => {
+ resolve(themes);
+ }, 200);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+}
diff --git a/brain/utility/DocTools.php b/brain/utility/DocTools.php
deleted file mode 100644
index 4c71f45..0000000
--- a/brain/utility/DocTools.php
+++ /dev/null
@@ -1,144 +0,0 @@
-getClientFileName(), $directory);
- // $extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
-
- // see http://php.net/manual/en/function.random-bytes.php
- // $basename = bin2hex(random_bytes(8));
- // $filename = sprintf("%s.%0.8s", $basename, $extension);
-
- // echo "**FILE** " . $file->getClientFileName();
-
- $file->moveTo($directory . '/' . urlencode($file->getClientFileName()));
- } catch (RuntimeException $e) {
- echo 'ERROR ' . $e->getMessage();
-
- // echo "failed to upload image: " . $e->getMessage();
- // throw new Error("Failed to upload image file");
- }
- }
-}
diff --git a/brain/utility/HandleCors.php b/brain/utility/HandleCors.php
deleted file mode 100644
index 78207e4..0000000
--- a/brain/utility/HandleCors.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getSettings();
- if ($settings['global']['externalAPI']) {
- //echo "API STATUS: " . $settings["global"]["externalAPI"];
- if ($settings['global']['externalAPI'] == 'true') {
- //echo "API ACCESS ACTIVE";
- // checks to see if origin is set
- if (isset($_SERVER['HTTP_ORIGIN'])) {
- // You can decide if the origin in $_SERVER['HTTP_ORIGIN']
- //is something you want to allow, or as we do here, just allow all
- header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
- } else {
- //No HTTP_ORIGIN set, so we allow any. You can disallow if needed here
- //never allow just any domain, so turn CORS off if no No HTTP_ORIGIN is set
- //header("Access-Control-Allow-Origin: *");
- }
-
- header('Access-Control-Allow-Credentials: true');
- header('Access-Control-Max-Age: 600'); // cache for 10 minutes
-
- if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
- if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
- header(
- 'Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT'
- );
- } //Make sure you remove those you do not want to support
-
- if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
- header(
- "Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
- );
- }
-
- //Just exit with 200 OK with the above headers for OPTIONS method
- exit(0);
- }
- } else {
- //echo "API ACCESS ACTIVE";
- }
- } else {
- //value doesn't exist, so whatevs
- //echo "API ACCESS VALUE NOT PRESENT";
- }
- } else {
- //init state, so chill
- }
- }
-}
diff --git a/brain/utility/Mailer.php b/brain/utility/Mailer.php
deleted file mode 100644
index 1c4afa1..0000000
--- a/brain/utility/Mailer.php
+++ /dev/null
@@ -1,95 +0,0 @@
-getSettings();
- $mailConfig = $settings['email'];
- $mail = new PHPMailer();
-
- switch ($body['mail_task']) {
- case 'TESTING':
- $html = "
Hi! It's Fipamo! " .
- "It's just a test " .
- $body['content'];
- $member = Session::get('member');
- $mail->addAddress($member['email'], ''); //pull email address from current user
- $mail->Subject = 'A test email';
- break;
- case 'SEND_SECRET':
- $html = "Hi! It's Fipamo! " .
- 'This is your secret key. ' .
- '' .
- $body['secret'] .
- ' ' .
- ' Use this key to reset your password.';
- $mail->addAddress($body['email'], ''); //pull email address from current user
- $mail->Subject = "Shhhh! It's a secret!";
- break;
- default:
- return $result = [
- 'type' => 'noMailService',
- 'message' => 'Mail task is undefined. What are you doing??',
- ];
- break;
- }
-
- //set values based on current active protocol
- switch ($mailConfig['active']) {
- case 'option-smtp':
- $mail->setFrom($mailConfig['smtp']['email'], 'System Email');
- $mail->Host = 'playvicio.us';
- $mail->Username = $mailConfig['smtp']['email'];
- $mail->Password = $mailConfig['smtp']['password'];
-
- break;
- case 'option-mg':
- $mail->setFrom($mailConfig['mailgun']['domain'], 'No Reply');
- $mail->Host = 'smtp.mailgun.org';
- $mail->Username = $mailConfig['mailgun']['domain'];
- $mail->Password = $mailConfig['mailgun']['key'];
- break;
- default:
- //no mail service
- return $result = [
- 'type' => 'noMailService',
- 'message' => 'Mail is not configured. Handle that.',
- ];
- break;
- }
-
- $mail->Body = $html;
- $mail->IsHTML(true);
- $mail->isSMTP();
- $mail->SMTPAuth = true;
- $mail->SMTPSecure = 'ssl';
- $mail->Port = 465;
-
- // Uncomment for debug info
- //$mail->SMTPDebug = 4;
-
- /* Finally send the mail. */
- try {
- $mail->send();
- $result = ['type' => 'mailSent', 'message' => 'Message Away!'];
- } catch (Exception $e) {
- //echo $e->errorMessage();
- $result = [
- 'type' => 'mailNotSent',
- 'message' => 'Message Not Away!',
- 'error' => $e->errorMessage(),
- ];
- }
-
- return $result;
- }
-}
diff --git a/brain/utility/Maintenance.php b/brain/utility/Maintenance.php
deleted file mode 100644
index c3e4f40..0000000
--- a/brain/utility/Maintenance.php
+++ /dev/null
@@ -1,102 +0,0 @@
-open(
- '../config/backups/latest_back.zip',
- \ZipArchive::CREATE | \ZipArchive::OVERWRITE
- );
- //gather data and path info for md pages
- $pagePath = '../content/pages';
- $yearPaths = glob($pagePath . '/*', GLOB_ONLYDIR);
- foreach ($yearPaths as $years) {
- $year = explode('/', $years);
- //grap the index and save it
- if (trim($year[3]) == 'start') {
- $options = [
- 'add_path' => 'content/pages/' . $year[3] . '/',
- 'remove_all_path' => true,
- ];
- $zip->addGlob($years . '/*.md', GLOB_BRACE, $options);
- }
- $monthsPath = glob($pagePath . '/' . $year[3] . '/*', GLOB_ONLYDIR);
- foreach ($monthsPath as $months) {
- $month = explode('/', $months);
- //once info is collected, add md pages to zip
- $options = [
- 'add_path' => 'content/pages/' . $year[3] . '/' . $month[4] . '/',
- 'remove_all_path' => true,
- ];
- $zip->addGlob($months . '/*.md', GLOB_BRACE, $options);
- }
- }
-
- //gather data and path info for blog images
- $blogImagesPath = '../public/assets/images/blog';
- $yearPaths = glob($blogImagesPath . '/*', GLOB_ONLYDIR);
- foreach ($yearPaths as $years) {
- $year = explode('/', $years);
- $monthsPath = glob($blogImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
- foreach ($monthsPath as $months) {
- $month = explode('/', $months);
- //once info is collected, add images pages to zip
- $options = [
- 'add_path' => 'public/assets/images/blog/' . $year[5] . '/' . $month[6] . '/',
- 'remove_all_path' => true,
- ];
- $zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
- }
- }
-
- //gather data and path info for user images
- $userImagesPath = '../public/assets/images/user';
- $yearPaths = glob($userImagesPath . '/*', GLOB_ONLYDIR);
- foreach ($yearPaths as $years) {
- $year = explode('/', $years);
- $monthsPath = glob($userImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
- foreach ($monthsPath as $months) {
- $month = explode('/', $months);
- //once info is collected, add images pages to zip
- $options = [
- 'add_path' => 'public/assets/images/user/' . $year[5] . '/' . $month[6] . '/',
- 'remove_all_path' => true,
- ];
- $zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
- }
- }
-
- //add directory for settings and save them
- $zip->addEmptyDir('settings');
- $zip->addFile('../config/settings.json', 'settings/settings.json');
- $zip->addFile('../config/folks.json', 'settings/folks.json');
- $zip->addFile('../config/tags.json', 'settings/tags.json');
- //save zip file
- $zip->close();
-
- //update settings file with latest back up date
- $updated = Carbon::now();
- Settings::updateGlobalData(
- 'last_backup',
- $updated->format("Y-m-d\TH:i:sP")
- );
-
- $result = ['message' => 'Backup created. THIS IS A SAFE SPACE!'];
- return $result;
- }
-}
diff --git a/brain/utility/Setup.php b/brain/utility/Setup.php
deleted file mode 100644
index 5d8c544..0000000
--- a/brain/utility/Setup.php
+++ /dev/null
@@ -1,230 +0,0 @@
-getParsedBody();
- $handle = $body['new_member_handle'];
- $email = $body['new_member_email'];
- $pass = $body['new_member_pass'];
- $title = $body['new_member_title'];
-
- $now = Carbon::now();
- //setup folks config
- $hash = password_hash($pass, PASSWORD_DEFAULT);
- $newFolks[0]['id'] = 0;
- $newFolks[0]['handle'] = $handle;
- $newFolks[0]['email'] = $email;
- $newFolks[0]['password'] = $hash;
- $newFolks[0]['key'] = password_hash($email, PASSWORD_DEFAULT);
- $newFolks[0]['secret'] = StringTools::randomString(12);
- $newFolks[0]['role'] = 'hnic';
- $newFolks[0]['created'] = $now->format("Y-m-d\TH:i:sP");
- $newFolks[0]['updated'] = $now->format("Y-m-d\TH:i:sP");
- //set up settings config
- $newSettings['global']['title'] = $title;
-
- //create index file
- //$rightNow = $now->format("Y-m-d\TH:i:sP");
- //var_dump($now->format("Y-m-d\TH:i:sP"));
- $index = [
- 'id' => 1,
- 'uuid' => StringTools::createUUID(),
- 'title' => 'FIRST!',
- 'feature' => '/assets/images/global/default-bg.jpg',
- 'files' => '',
- 'path' => 'content/pages/start',
- 'layout' => 'index',
- 'tags' => 'start, welcome',
- 'author' => $handle,
- 'created' => $now->format("Y-m-d\TH:i:sP"),
- 'updated' => $now->format("Y-m-d\TH:i:sP"),
- 'deleted' => 'false',
- 'slug' => 'first',
- 'menu' => 'false',
- 'featured' => 'false',
- 'published' => 'true',
- 'content' => "# F**k Yes \n\nIf you're seeing this, you're up and running. NICE WORK!\n\nFrom here, feel free to start dropping pages to your heart's content.\n\nFor some tips about using Fipamo, check out the ![docs](https://code.playvicio.us/Are0h/Fipamo/wiki/02-Usage)\n\nAll good? Feel free to edit this page to whatever you want!\n\nYOU'RE THE CAPTAIN NOW.",
- ];
-
- $freshIndex = DocTools::objectToMD($index);
-
- //once all files created, write down
-
- DocTools::writeSettings('../config/settings.json', $newSettings);
- DocTools::writeSettings('../config/folks.json', $newFolks);
- DocTools::writeSettings('../config/tags.json', []);
- DocTools::writePages(
- 'create',
- 'start',
- '../content/pages/start/index.md',
- $freshIndex
- );
-
- //if there is an older session file, get rid of it
- if (is_file('../content/.session')) {
- unlink('../content/.session');
- }
-
- $result = ['type' => 'blogInitGood', 'message' => 'Site Created'];
-
- return $result;
- }
-
- public static function restore($request)
- {
- $result = [
- 'type' => 'requestLame',
- 'message' => 'Still working on it.',
- ];
- $body = $request->getParsedBody();
-
- $backup = $request->getUploadedFiles();
- $file = $backup['backup-upload'];
- //NOTE: If this fails check 'post_max_size' in php.ini
- $size = $file->getSize();
- $name = $file->getClientFileName();
-
- //park it so it can be read
- $file->moveTo('../content' . '/' . $name);
-
- //open it and get files to verify user
- $zip = new \ZipArchive();
- if ($zip->open('../content' . '/' . $name) === true) {
- $folks = json_decode($zip->getFromName('settings/folks.json'), true);
- $found = find($folks, ['handle' => $body['restore_member_handle']]);
-
- //if member is found in back up, check pass
- if ($found) {
- if (password_verify($body['restore_member_pass'], $found['password'])) {
- //backup verified, restore site
-
- //set new secret key for older folks configs
- $newFolks = [];
- if (!isset($found['secret'])) {
- $found['secret'] = StringTools::randomString(12);
- }
- array_push($newFolks, $found);
- //dump files in folder
- $zip->extractTo('../content');
-
- //move to appropriate spots
- /*
- rename(
- "../content/settings/settings.json",
- "../config/settings.json"
- );
- */
-
- //load up old config file
- $newConfig = json_decode(
- file_get_contents('../content/settings/settings.json'),
- true
- );
- //check for key, add if not there
- if (!isset($newConfig['global']['externalAPI'])) {
- $newConfig['global']['externalAPI'] = 'false';
- }
- //write new config file
- DocTools::writeSettings('../config/settings.json', $newConfig);
-
- //rename("../content/settings/folks.json", "../config/folks.json");
- DocTools::writeSettings('../config/folks.json', $newFolks);
-
- rename('../content/settings/tags.json', '../config/tags.json');
-
- //images path for blog and user
- $blogImagePath = '../public/assets/images/blog';
- $userImagePath = '../public/assets/images/user';
-
- //check to see if image dirs are empty, if not chill
- if ($globs = glob($blogImagePath . '/*')) {
- //directory not empty, relax
- } else {
- rename('../content/public/assets/images/blog', $blogImagePath);
- }
-
- if ($globs = glob($userImagePath . '/*')) {
- //directory not empty, relax
- } else {
- rename('../content/public/assets/images/user', $userImagePath);
- }
-
- rename('../content/content/pages/', '../content/pages');
-
- //legacy check for old file structure
- if (is_file('../content/pages/index.md')) {
- if (!is_dir('../content/pages/start')) {
- //Directory does not exist, so lets create it.
- mkdir('../content/pages/start', 0755, true);
- //move start page to appropriate spot
- rename(
- '../content/pages/index.md',
- '../content/pages/start/index.md'
- );
- }
- } else {
- //chill
- }
-
- //clean up
-
- DocTools::deleteFolder('../content/settings');
- DocTools::deleteFolder('../content/public');
- DocTools::deleteFolder('../content/content');
- $result = [
- 'type' => 'requestGood',
- 'message' => 'Site Restored! Redirecting',
- ];
- } else {
- $result = [
- 'type' => 'requestLame',
- 'message' => 'Check that password, champ.',
- ];
- }
- } else {
- $result = [
- 'type' => 'requestLame',
- 'message' => 'No member found by that name, hoss',
- ];
- }
-
- $zip->close();
- $zipPath = '../content/' . $name;
- //trash zip when done
- unlink($zipPath);
- } else {
- $result = [
- 'type' => 'requestLame',
- 'message' => 'Could not open backup. RATS!',
- ];
- }
- return $result;
- }
-}
diff --git a/brain/utility/Sorting.php b/brain/utility/Sorting.php
deleted file mode 100644
index aa38c14..0000000
--- a/brain/utility/Sorting.php
+++ /dev/null
@@ -1,264 +0,0 @@
-getContents();
- foreach ($pages as $page) {
- $temp = [];
- $temp = explode(',', $page['tags']);
- foreach ($temp as $tag) {
- $label = trim($tag);
- if (!find(self::$p_tags, ['tag_name' => $label])) {
- array_push(self::$p_tags, [
- 'tag_name' => $label,
- 'slug' => StringTools::safeString($label),
- 'pages' => self::tagPages($label, $pages),
- ]);
- }
- }
- }
-
- return self::$p_tags;
- }
-
- private static function tagPages($tag, $pages)
- {
- $tagged = [];
- foreach ($pages as $page) {
- if (strpos($page['tags'], $tag) !== false) {
- array_push($tagged, [
- 'title' => $page['title'],
- 'slug' => $page['slug'],
- 'path' => $page['path'],
- 'feature' => $page['feature'],
- ]);
- }
- }
-
- return $tagged;
- }
-
- public static function archive()
- {
- $pages = (new Book('../content/pages'))->getContents();
- $years = [];
- $archive = [];
- foreach ($pages as $page) {
- // $year = date("Y", date($page["rawCreated"]));
- $date = explode('/', $page['path']);
- // echo $page["title"] . " : " . $year . "\n";
- if (!find($years, ['year' => trim($date[0])])) {
- $findPages = filter($pages, ['createdYear' => trim($date[0])]);
- // var_dump($findPages);
- array_push(
- $years,
- [
- 'year' => trim($date[0]),
- 'count' => count($findPages),
- ]
- );
- }
- }
- foreach ($years as $year) {
- $sorted = [];
- $filtered = filter($pages, ['createdYear' => $year['year']]);
-
- foreach ($filtered as $obj) {
- $month = date('m', date($obj['rawCreated']));
- if (!find($sorted, ['month' => $month])) {
- $perMonth = filter(
- $pages,
- [
- 'path' => $year['year'] . '/' . $month,
- 'deleted' => false,
- 'published' => true,
- 'layout' => 'page',
- ]
- );
- array_push($sorted, [
- 'month' => $month,
- 'full_month' => date('F', date($obj['rawCreated'])),
- 'count' => count($perMonth),
- 'pages' => $perMonth,
- ]);
- }
- }
- array_push(self::$p_archive, [
- 'year' => $year['year'],
- 'year_data' => $sorted,
- ]);
- }
-
- return self::$p_archive;
- }
-
- public static function page($page)
- {
- $config = new Settings();
- $settings = $config->getSettings();
- $pageOption = [];
-
- $pageInfo = [
- 'keywords' => isset($settings['global']['keywords'])
- ? $settings['global']['keywords']
- : 'fipamo, blog, jamstack, php, markdown, js',
- 'description' => $settings['global']['descriptions'],
- 'image' => $settings['global']['base_url'] . $settings['global']['background'],
- 'baseURL' => $settings['global']['base_url'],
- ];
-
- $taglist = explode(',', $page['tags']);
- $tags = [];
- foreach ($taglist as $tag) {
- $label = trim($tag);
- array_push($tags, [
- 'label' => $label . ' ',
- 'slug' => StringTools::safeString($label),
- ]);
- }
-
- $meta = [
- 'who' => $page['author'],
- 'when' => $page['created'],
- 'tags' => $tags,
- ];
-
- // render markdown content and clean it
- $parser = new Parser();
- $rendered = $parser->parse($page['content']);
- $sanitizer = \HtmlSanitizer\Sanitizer::create([
- 'extensions' => ['basic', 'image', 'list', 'code'],
- 'tags' => [
- 'img' => [
- 'allowed_attributes' => ['src', 'alt', 'title', 'class'],
- 'allowed_hosts' => null,
- ],
- ],
- ]);
-
- $preclean = $sanitizer->sanitize($rendered->getContent());
-
- // just clean renderd string for now, Sanitize doesn't like relative img urls
- // so another option is needed
- $cleaned = strip_tags($rendered->getContent(), [
- 'a',
- 'br',
- 'p',
- 'strong',
- 'br',
- 'img',
- 'iframe',
- 'ul',
- 'li',
- 'i',
- 'em',
- 'h1',
- 'h2',
- 'h3',
- 'pre',
- 'code',
- ]);
-
- // if page feature isn't empty, find image from list and set it as background image
- // if it is empty, just use global background
- if ($page['feature'] != '' || $page['feature'] != null) {
- $media = explode(',', $page['feature']);
- $set = false;
- foreach ($media as $file) {
- $item = trim($file);
- $ext = pathinfo($item, PATHINFO_EXTENSION);
-
- if ($ext != 'mp4' && !$set) {
- $pageInfo['image'] = $pageInfo['baseURL'] . $item;
- $set = true;
- }
- }
- }
-
- if ($page['layout'] == 'index') {
- // $template = $this->theme . "/index.twig";
- // $location = "../public/index.html";
- // $dir = null;
-
- $recent = [];
- $featured = [];
- $limit = 4;
- $pages = (new Book())->getContents();
- foreach ($pages as $item) {
- if (!$item['deleted'] &&
- $item['published'] &&
- $item['menu'] != 'true'
- ) {
- if (count($recent) < $limit) {
- array_push($recent, [
- 'path' => $item['path'],
- 'slug' => $item['slug'],
- 'title' => $item['title'],
- 'feature' => $item['feature'],
- ]);
- }
-
- if ($item['featured'] == true) {
- if (count($featured) < $limit) {
- array_push($featured, [
- 'path' => $item['path'],
- 'slug' => $item['slug'],
- 'title' => $item['title'],
- 'feature' => $item['feature'],
- ]);
- }
- }
- }
- }
-
- $pageOptions = [
- 'title' => $page['title'],
- 'background' => $page['feature'],
- 'content' => $page['html'], // $cleaned,
- 'meta' => $meta,
- 'recent' => $recent,
- 'featured' => $featured,
- 'info' => $pageInfo,
- 'menu' => $settings['menu'],
- 'dynamicRender' => $settings['global']['dynamicRender'],
- 'media' => $page['media'],
- 'files' => $page['docs'],
- ];
- } else {
- // $template = $this->theme . "/page.twig";
- // $location = "../public/" . $page["path"] . "/" . $page["slug"] . ".html";
- // $dir = "../public/" . $page["path"];
- $pageOptions = [
- 'title' => $page['title'],
- 'background' => $page['feature'],
- 'content' => $page['html'], // $cleaned,
- 'meta' => $meta,
- 'info' => $pageInfo,
- 'menu' => $settings['menu'],
- 'dynamicRender' => $settings['global']['dynamicRender'],
- 'media' => $page['media'],
- 'files' => $page['docs'],
- ];
- }
-
- return $pageOptions;
- }
-}
diff --git a/brain/utility/StringTools.php b/brain/utility/StringTools.php
deleted file mode 100644
index 76ae2ac..0000000
--- a/brain/utility/StringTools.php
+++ /dev/null
@@ -1,131 +0,0 @@
-parse($entry);
- $sanitizer = HtmlSanitizer\Sanitizer::create([
- 'extensions' => ['basic', 'image', 'list', 'code'],
- 'tags' => [
- 'img' => [
- 'allowed_attributes' => ['src', 'alt', 'title', 'class'],
- 'allowed_hosts' => null,
- ],
- ],
- ]);
-
- $preclean = $sanitizer->sanitize($rendered->getContent());
-
- $cleaned = strip_tags($rendered->getContent(), [
- 'a',
- 'br',
- 'p',
- 'strong',
- 'br',
- 'img',
- 'iframe',
- 'ul',
- 'li',
- 'i',
- 'h1',
- 'h2',
- 'h3',
- 'pre',
- 'code',
- ]);
-
- return $cleaned;
- }
-
- public static function safeString($string)
- {
- return strtolower(
- trim(
- preg_replace(
- '~[^0-9a-z]+~i',
- '_',
- html_entity_decode(
- preg_replace(
- '~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i',
- '$1',
- htmlentities($string, ENT_QUOTES, 'UTF-8')
- ),
- ENT_QUOTES,
- 'UTF-8'
- )
- ),
- '-'
- )
- );
- }
-
- public static function randomString(int $length)
- {
- $alphanum = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- $special = '*&!@%^#$';
- $alphabet = $alphanum . $special;
- $random = openssl_random_pseudo_bytes($length);
- $alphabet_length = strlen($alphabet);
- $string = '';
- for ($i = 0; $i < $length; ++$i) {
- $string .= $alphabet[ord($random[$i]) % $alphabet_length];
- }
-
- //secret needs to be a valid token
- if ($length == 12) {
- try {
- $secret = Token::create(12, $string, time() + 3600, 'localhost');
- return $string;
- } catch (BuildException $e) {
- //bad secret, so try agiain
- //echo "BAD STRING";
- return self::randomString(12);
- }
-
- if (Token::validate($key, $string)) {
- return $string;
- } else {
- return self::randomString(12);
- }
- }
- }
-
- private static function checkSpecial($string)
- {
- $specials = ['*', '&', '!', '@', '%', '^', '#', '$'];
- $valid = false;
- foreach ($specials as $item) {
- if (strpos($string, $item)) {
- return $valid = true;
- }
- }
- return $valid;
- }
-}
diff --git a/brain/views/book-index.pug b/brain/views/book-index.pug
new file mode 100644
index 0000000..e53eaa3
--- /dev/null
+++ b/brain/views/book-index.pug
@@ -0,0 +1,57 @@
+extends frame
+block main-content
+ #post-index
+ #post-index-wrapper
+ #post-index-menu
+ - if(postFilter=='all')
+ a.current-filter(href="/@/dashboard/page/list/all")= "All Pages ("+page_info.all+")"
+ - else
+ a(href="/@/dashboard/page/list/all")= "All Pages ("+page_info.all+")"
+ | .
+ - if(postFilter=='published')
+ a.current-filter(href="/@/dashboard/page/list/published")= "Published ("+page_info.published+")"
+ - else
+ a(href="/@/dashboard/page/list/published")= "Published ("+page_info.published+")"
+ | .
+ - if(postFilter=='deleted')
+ a.current-filter(href="/@/dashboard/page/list/deleted")= "Deleted ("+page_info.deleted+")"
+ - else
+ a(href="/@/dashboard/page/list/deleted")= "Deleted ("+page_info.deleted+")"
+
+ a.add-new-post(href="/@/dashboard/page/add/new") +
+ label Create New Post
+ #posts-list
+ - var index = 0;
+ - for ( index; index < items.length; index++)
+ a.page-link(href="/@/dashboard/page/edit/"+items[index].page.uuid id=items[index].uuid)
+ div.page-bg(style="background: #fc6399 url("+items[index].page.feature+") no-repeat center center / cover")
+ #meta
+ - var menu = String(items[index].page.menu)
+ - var published = String(items[index].page.published)
+ - var featured = String(items[index].page.featured)
+ span= items[index].date
+ label= items[index].page.title
+ br
+ #options
+ span.meta-options(data-active=menu) MENU ITEM
+ span.meta-options(data-active=published) PUBLISHED
+ span.meta-options(data-active=featured) FEATURED
+
+
+ - var next = parseInt(page_index, 10) + 1
+ - var prev = parseInt(page_index, 10) - 1
+ - if(next > page_count) next = 1
+ - if(prev <= 0) prev = page_count
+ - if(page_count > 1)
+ br
+ .paginate
+ a.page-btns(href="/@/dashboard/page/list/"+postFilter+"/"+prev)
+ svg(viewBox="0 0 20 20" class="icons")
+ use(xlink:href='/assets/images/global/sprite.svg#entypo-chevron-left')
+
+ span.count= "PAGE "+page_index+" OF "+page_count
+
+ a.page-btns(href="/@/dashboard/page/list/"+postFilter+"/"+next)
+ svg(viewBox="0 0 20 20" class="icons")
+ use(xlink:href='/assets/images/global/sprite.svg#entypo-chevron-right')
+
diff --git a/brain/views/dash/_frame.old.twig b/brain/views/dash/_frame.old.twig
deleted file mode 100644
index 4f96798..0000000
--- a/brain/views/dash/_frame.old.twig
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
- {% block title %}
- {{ title }}
- {% endblock %}
-
- {% block stylesheets %}{% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% if status %}
-
- {% endif %}
- {% apply spaceless %}
- {% block mainContent %}{% endblock %}
- {% endapply %}
-
-
-
-
-
-
-
- {% block javascripts %}{% endblock %}
-
-
diff --git a/brain/views/dash/_frame.twig b/brain/views/dash/_frame.twig
deleted file mode 100644
index f941c1e..0000000
--- a/brain/views/dash/_frame.twig
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
- {% block title %}
- {{ title }}
- {% endblock %}
-
- {% block stylesheets %}{% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% if status %}
-
-
- {% apply spaceless %}
- {% block mainContent %}{% endblock %}
- {% endapply %}
-
-
- {% block javascripts %}{% endblock %}
-
-
diff --git a/brain/views/dash/book.twig b/brain/views/dash/book.twig
deleted file mode 100644
index 813b291..0000000
--- a/brain/views/dash/book.twig
+++ /dev/null
@@ -1,102 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
-
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
\ No newline at end of file
diff --git a/brain/views/dash/email.twig b/brain/views/dash/email.twig
deleted file mode 100644
index 0fc58cb..0000000
--- a/brain/views/dash/email.twig
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
- {{title}}
-
-
-
-
-
-
-
- {# edge wrapper #}
-
-
-
- {# info table start #}
-
-
-
-
- {{ header }}
-
-
-
-
-
-
- {{ content }}
-
-
-
-
-
-
- {{ footer }}
-
-
-
-
- {# info table end #}
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/brain/views/dash/forms/login.twig b/brain/views/dash/forms/login.twig
deleted file mode 100644
index 5c2b285..0000000
--- a/brain/views/dash/forms/login.twig
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
diff --git a/brain/views/dash/init.twig b/brain/views/dash/init.twig
deleted file mode 100644
index 121f8e6..0000000
--- a/brain/views/dash/init.twig
+++ /dev/null
@@ -1,48 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
-
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
\ No newline at end of file
diff --git a/brain/views/dash/navigation.twig b/brain/views/dash/navigation.twig
deleted file mode 100644
index feb5ae2..0000000
--- a/brain/views/dash/navigation.twig
+++ /dev/null
@@ -1,43 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
-{% endblock %}
-
-{% block mainContent %}
-
-
-
- {% for item in menu %}
-
-
-
-
-
{{item.title}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% endfor %}
-
-
-
-{% endblock %}
-
-{% block javascripts %}
-
-{% endblock %}
diff --git a/brain/views/dash/page-edit.twig b/brain/views/dash/page-edit.twig
deleted file mode 100644
index f069689..0000000
--- a/brain/views/dash/page-edit.twig
+++ /dev/null
@@ -1,198 +0,0 @@
-{% extends "dash/_frame.twig" %}
-{#
- if page is in 'edit' mode, set variables
- if not, just make them empty
- #}
-{% if mode == 'edit' %}
- {% set id = page['id'] %}
- {% set uuid = page['uuid'] %}
- {% set slug = page['slug'] %}
- {% set layout = page['layout'] %}
- {% set feature = page['feature'] %}
- {% set title = page['title'] %}
- {% set tags = page['tags'] %}
- {% set content = page['content'] %}
- {% set date = page['created'] %}
- {% set updated = page['updated'] %}
- {% set media = page['media'] %}
- {% set files = page['docs'] %}
-{% else %}
- {% set id = '' %}
- {% set uuid = '' %}
- {% set slug = '' %}
- {% set layout = 'page' %}
- {% set feature = '' %}
- {% set title = '' %}
- {% set tags = '' %}
- {% set content = '' %}
- {% set date = '' %}
- {% set updated = '' %}
- {% set media = '' %}
- {% set files = '' %}
-{% endif %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
-
-
-
- {% if page['feature'] == null %}
-
-
-
- DRAG AND DROP FILES OR CLICK TO SELECT
-
- IMAGES AND VIDEO
-
- FILES
-
-
-
-
- {% else %}
-
-
-
- DRAG AND DROP FILES OR CLICK TO SELECT
-
- IMAGES AND VIDEO
-
- {% if media|length > 1 %}
- {% for item in media %}
- {% set fileName = item.file|split('/') %}
- {% if item.type == "mp4" %}
-
-
- X
-
- {% else %}
-
- X
-
- {% endif %}
- {% endfor %}
- {% else %}
- {% if media[0] != '' %}
- {% set fileName = media[0].file|split('/') %}
- {% if media[0].type == "mp4" %}
-
- X
-
-
- {% else %}
-
- X
-
- {% endif %}
- {% else %}
- {% endif %}
- {% endif %}
-
-
- FILES
-
- {% if files|length > 1 %}
- {% for item in files %}
- {% set fileName = item.file|split('/') %}
- {% if item.type == "mp3" %}
-
- X
-
- {% else %}
-
- X
-
- {% endif %}
- {% endfor %}
- {% else %}
- {% if files[0] != '' %}
- {% set fileName = files[0].file|split('/') %}
- {% if files[0].type == "mp3" %}
-
- X
-
- {% else %}
-
- X
-
- {% endif %}
-
- {% else %}
-
- {% endif %}
- {% endif %}
-
-
-
-
-
- {% endif %}
-
-
-
-
- {% apply spaceless %}
- {{ include("dash/partials/editor.twig") }}
- {% endapply %}
-
-
-
-
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
diff --git a/brain/views/dash/partials/editor.twig b/brain/views/dash/partials/editor.twig
deleted file mode 100644
index 0b3455d..0000000
--- a/brain/views/dash/partials/editor.twig
+++ /dev/null
@@ -1,45 +0,0 @@
-
- B
- I
- D
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% if mode == "edit" %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% else %}
-
-
-
-
-
- {% endif %}
-
-
-
-
diff --git a/brain/views/dash/partials/index.twig b/brain/views/dash/partials/index.twig
deleted file mode 100644
index 3258b12..0000000
--- a/brain/views/dash/partials/index.twig
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
diff --git a/brain/views/dash/partials/mailforms.twig b/brain/views/dash/partials/mailforms.twig
deleted file mode 100644
index 9c85c5e..0000000
--- a/brain/views/dash/partials/mailforms.twig
+++ /dev/null
@@ -1,31 +0,0 @@
-{% if mailOption == "option-smtp" %}
-
-
-
-
-
-
-
-
-
-{% elseif(mailOption == 'option-mg') %}
-
-
-
-
-
-
-
-
-
-{% else %}
-
-
-
-
-
-
-
-
-
-{% endif %}
\ No newline at end of file
diff --git a/brain/views/dash/partials/navigation.twig b/brain/views/dash/partials/navigation.twig
deleted file mode 100644
index 10ffc29..0000000
--- a/brain/views/dash/partials/navigation.twig
+++ /dev/null
@@ -1,25 +0,0 @@
-
diff --git a/brain/views/dash/partials/options.twig b/brain/views/dash/partials/options.twig
deleted file mode 100644
index 94cfe4a..0000000
--- a/brain/views/dash/partials/options.twig
+++ /dev/null
@@ -1,42 +0,0 @@
-{% if page['menu'] %}
- {% set menu = 'true' %}
-{% else %}
- {% set menu = 'false' %}
-{% endif %}
-
-{% if page['featured'] %}
- {% set featured = 'true' %}
-{% else %}
- {% set featured = 'false' %}
-{% endif %}
-
-{% if page['published'] %}
- {% set published = 'true' %}
-{% else %}
- {% set published = 'false' %}
-{% endif %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/brain/views/dash/partials/recent-meta.twig b/brain/views/dash/partials/recent-meta.twig
deleted file mode 100644
index 0160efe..0000000
--- a/brain/views/dash/partials/recent-meta.twig
+++ /dev/null
@@ -1,43 +0,0 @@
-{% if page.menu == 'true' %}
- {% set menu = "true" %}
-{% else %}
- {% set menu = "false" %}
-{% endif %}
-{% if page.published == 'true' %}
- {% set published = "true" %}
-{% else %}
- {% set published = "false" %}
-{% endif %}
-{% if page.featured == 'true' %}
- {% set featured = "true" %}
-{% else %}
- {% set featured = "false" %}
-{% endif %}
-
-
-
- {{ page.updated }}
-
-
-
- {{ page.title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/brain/views/dash/reset-password.twig b/brain/views/dash/reset-password.twig
deleted file mode 100644
index d784fce..0000000
--- a/brain/views/dash/reset-password.twig
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
-
-
-
-
-
-
-
-
-
-
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
\ No newline at end of file
diff --git a/brain/views/dash/settings.twig b/brain/views/dash/settings.twig
deleted file mode 100644
index 67333d2..0000000
--- a/brain/views/dash/settings.twig
+++ /dev/null
@@ -1,192 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% if apiStatus is defined and apiStatus == "true" %}
-
-
-
-
- EXTERNAL API ACCESS ENABLED
-
-
- {% else %}
-
-
-
-
- EXTERNAL API ACCESS NOT ENABLED
-
-
- {% endif %}
-
-
-
-
-
- {% if dynamicRenderStatus is defined and dynamicRenderStatus == "true" %}
-
-
-
-
- DYNAMIC PAGE RENDERING
-
-
- {% else %}
-
-
-
-
- STATIC PAGE RENDERING
-
-
- {% endif %}
-
-
-
-
-
-
-
-
-
-
EMAIL
- {% if mailOption == "option-none" or mailOption == "" %}
-
NONE
- {% else %}
-
NONE
- {% endif %}
- {% if mailOption == "option-mg" or mailOption == "" %}
-
MAILGUN
- {% else %}
-
MAILGUN
- {% endif %}
- {% if mailOption == "option-smtp" or mailOption == "" %}
-
SMTP
- {% else %}
-
SMTP
- {% endif %}
-
- {% apply spaceless %}
- {{ include("dash/partials/mailforms.twig") }}
- {% endapply %}
-
TEST MAIL
-
-
-
-
-
-
-
-
API KEY
-
- {{member['key']}}
-
-
-
-
-
FORM TOKEN
-
- {{ftoken}}
-
-
-
-
-
-
-
-
- BACK UP YOUR SITE
-
-
- {% if lastBackup != '' %}
-
- {% else %}
-
span No back ups. Frowny face.
- {% endif %}
-
-
-
-
-
-
-
-
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
\ No newline at end of file
diff --git a/brain/views/dash/start.twig b/brain/views/dash/start.twig
deleted file mode 100644
index 4f822ca..0000000
--- a/brain/views/dash/start.twig
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends "dash/_frame.twig" %}
-
-{% block title %}
- {{ title }}
-{% endblock %}
-
-{% block stylesheets %}
-
- {% endblock %}
-
- {% block mainContent %}
- {% if status %}
- {% apply spaceless %}
- {{ include("dash/partials/index.twig") }}
- {% endapply %}
- {% else %}
- {{ include("dash/forms/login.twig") }}
- {% endif %}
- {% endblock %}
-
- {% block javascripts %}
-
- {% endblock %}
diff --git a/brain/views/email/base.pug b/brain/views/email/base.pug
new file mode 100644
index 0000000..8c0005a
--- /dev/null
+++ b/brain/views/email/base.pug
@@ -0,0 +1,94 @@
+doctype strict
+head
+ meta(http-equiv='Content-Type' content='text/html; charset=utf-8')
+ meta(name='viewport' content='width=device-width, initial-scale=1.0')
+ title #{title}
+ style(type='text/css').
+ /* reset */
+ #outlook a {
+ padding: 0;
+ }
+ /* Force Outlook to provide a "view in browser" menu link. */
+ .ExternalClass {
+ width: 100%;
+ }
+ /* Force Hotmail to display emails at full width */
+ .ExternalClass,
+ .ExternalClass p,
+ .ExternalClass span,
+ .ExternalClass font,
+ .ExternalClass td,
+ .ExternalClass div {
+ line-height: 100%;
+ }
+ /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
+ p {
+ margin: 0;
+ padding: 0;
+ font-size: 0px;
+ line-height: 0px;
+ }
+ /* squash Exact Target injected paragraphs */
+ table td {
+ border-collapse: collapse;
+ }
+ /* Outlook 07, 10 padding issue fix */
+ table {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+ /* remove spacing around Outlook 07, 10 tables */
+ /* bring inline */
+ img {
+ display: block;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+ a img {
+ border: none;
+ }
+ a {
+ text-decoration: none;
+ color: #000001;
+ }
+ /* text link */
+ a.phone {
+ text-decoration: none;
+ color: #000001 !important;
+ pointer-events: auto;
+ cursor: default;
+ }
+ /* phone link, use as wrapper on phone numbers */
+ span {
+ font-size: 13px;
+ line-height: 17px;
+ font-family: monospace;
+ color: #000001;
+ }
+ //if gte mso 9
+ style.
+ /* Target Outlook 2007 and 2010 */
+// body wrapper
+table(cellpadding='0' cellspacing='0' border='0' style='margin:0; padding:0; width:100%; line-height: 100% !important;')
+ tr
+ td(valign='top')
+ // edge wrapper
+ table(cellpadding='0' cellspacing='0' border='0' align='center' width='600' style='background: #374857;')
+ tr
+ td(valign='top' style='vertical-align: top;')
+ // /////////////////////////////////////////////////////
+ table(cellpadding='0' cellspacing='0' border='0' align='center' style='width:100%')
+ tr
+ td(valign='top' style='vertical-align: top;text-align: center; padding: 10px')
+ span(style='font-family: Arial,Helvetica Neue,Helvetica,sans-serif; color:#f5ab35; font-size:20px; font-weight: bold;')
+ | #{header}
+ tr
+ td(valign='top' style='vertical-align: top; background: #161d23; padding:10px;')
+ span(style='font-family: Arial,Helvetica Neue,Helvetica,sans-serif; color:#cecece; font-size:16px;')
+ | #{content}
+ tr
+ td(valign='top' style='vertical-align: top; padding: 10px;')
+ span(style='font-family: Arial,Helvetica Neue,Helvetica,sans-serif; color:#b2cce5; font-size:12px;')
+ | #{footer}
diff --git a/brain/views/error.pug b/brain/views/error.pug
new file mode 100644
index 0000000..b3cb2b1
--- /dev/null
+++ b/brain/views/error.pug
@@ -0,0 +1,8 @@
+extends frame
+block main-content
+ #error-index
+ br
+ label#message Ok, so this is... awkward
+ br
+ label#error= error
+
diff --git a/brain/views/frame.pug b/brain/views/frame.pug
new file mode 100644
index 0000000..de0b9a2
--- /dev/null
+++ b/brain/views/frame.pug
@@ -0,0 +1,35 @@
+doctype html
+html(xmlns='http://www.w3.org/1999/xhtml', lang='en', xml:lang="en")
+ head
+ title= title
+ meta(content="text/html;charset=utf-8", http-equiv="Content-Type")
+ meta(meta content="utf-8", http-equiv="encoding")
+ meta(name='viewport', content='width=device-width, initial-scale=1.0')
+ meta(name="keywords" content="")
+ meta(name="description" content="")
+ meta(http-equiv="content-type", content="text/html; charset=utf-8")
+ //meta(property="og:image" content="https://thetwelfth.house/base-assets/images/current.png")
+ //meta(name="twitter:image" content="https://thetwelfth.house/base-assets/images/current.png")
+ link(rel='stylesheet', href="/assets/css/dash.css", type='text/css')
+ body
+ #notifications.notifications
+ #notifyMessage.notifyMessage
+ .notify-icon
+ svg(viewBox="0 0 20 20" class="icons")
+ use#submit-update(xlink:href='/assets/images/global/sprite.svg#entypo-bell')
+ p#message-text This is a message
+ .main-container#main-content
+ section#dash-index-content
+ header#header
+ #wrapper
+ #left
+ a(href="/@/dashboard")
+ img#the-logo(src="/assets/images/global/the-logo.svg")
+ #right
+ -if(status)
+ include partials/dash-nav
+ block main-content
+ script(src='/assets/scripts/dashkit.min.js' type="text/javascript")
+ script(src='/assets/scripts/dash.min.js' type="text/javascript")
+
+
diff --git a/brain/views/front/_frame.twig b/brain/views/front/_frame.twig
deleted file mode 100644
index ffccaa2..0000000
--- a/brain/views/front/_frame.twig
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
- {% block title %}
- {{ title }}
- {% endblock %}
-
- {% block stylesheets %}{% endblock %}
-
-
- {% block mainContent %}{% endblock %}
-
-
-
-
-
-
- {% if options['showFooter'] is defined %}
-
- {% else %}
-
- {% endif %}
-
-
-
-{% block javascripts %}{% endblock %}