├── .gitignore
├── LICENSE
├── README.md
├── config-debug
├── config-release
├── debug.js
├── package.json
├── packages.js
├── release.js
└── views
├── layouts
├── default.html
├── default.html.js
└── default.html.json
├── pages
├── test.html
├── test.html.js
└── test.html.json
└── widgets
├── image.html
├── image.html.js
├── image.html.json
├── text.html
├── text.html.js
└── text.html.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # temp folder
30 | tmp
31 |
32 | # databases folder
33 | databases
34 |
35 | # vscode project
36 | .vscode/*
37 |
38 | # downloaded packages
39 | packages
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Enterprise
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ![MIT License][license-image]
3 |
4 | # Full-featured nodejs CMS
5 |
6 | ## Installation
7 |
8 | 1. install and run [mongodb](https://www.mongodb.org/downloads)
9 | 2. install graphicsmagick `brew install ghostscript` and `brew install graphicsmagick`, or [graphicsmagick on windows](http://www.graphicsmagick.org/download.html#download-sites)
10 | 3. download, unzip cms
11 | 4. cd inside folder, then `npm install` to download modules
12 | 5. run cms with `node debug.js` ,or `node release.js` in production
13 |
14 |
15 | ## Requirements
16 | - nodejs >= 4.0.0
17 | - running mongodb > 2
18 |
19 | ## Next Steps
20 | 1. goto http://localhost:8080/cmsadmin
21 | 2. click "sign up" and enter admin email/pass - this will create first admin user
22 | 3. log in
23 | 4. check config-debug, config-release to setup mailer, or app name (it is used as database name, if there is no database specified)
24 | 5. enjoy + check website [nodee.io](https://nodee.io) for more information and documentation
25 |
26 | 
27 |
28 | [Learn more here](https://nodee.io/docs/cms/concept)
29 |
30 | # Nodee CMS – the Concept
31 |
32 | Nodee CMS was born to handle almost any data viewing and the editing scenario. Yes, many good content management systems can do this, but it often means developing new plugins, data structures, types of data editors in the administration area, etc… So, here comes new CMS concept, build with modern technologies for modern websites.
33 |
34 | ## Always see what you are editing
35 | Editing content has to be easy for all content managers. Therefore, onsite (or inline) editing is like a must. We don’t want to go to content pages lists or menu settings forms located somewhere in the admin area, dedicated from the page design. We want to see changes immediately.
36 |
37 | ## Widgets everywhere
38 | Every template is a widget. Some are used like layouts, some like pages, but can be reused like widgets. If inside template HTML is defined widgets container like `
`, widgets can be added by content managers. Or using `` as fixed page partials. Every widget can have attributes. Attributes represent widget settings and can be edited by content managers.
39 |
40 | ## Templates – “2-way data binding”
41 | Same way as data are rendered, they are parsed and stored. Entire flow has 3 steps: render HTML --> manipulate HTML by content manager (send it back) --> parse, extract data and store in database.
42 | 1. Rendering - Let’s take a look at rendering closer (controller --> mapping --> html):
43 | - Widget (or template) controller – can manipulate model (model is by default CmsDocument), or directly send response
44 | - Data mapping – easy mapping via CSS selectors defines how data have to be rendered or injected to HTML template
45 | - HTML template – plain html, only very few special html tag are used (e.g. layout, widget, ...)
46 |
47 | 2. Editing content: ok, now we have rendered HTML page. So we can go to the content administration area and apply all widget editors on this HTML. As a result, we can change texts, images, lists, etc… Editors are defined by CSS selectors, and, of course, can be extended.
48 |
49 | 3. Parsing and Storing data (mapping --> parser --> storing):
50 | - Mapping - thanks to mapping and HTML separation, we can parse data back (by default, same mapping for rendering and parsing is used, but you can different if parse mapping is defined)
51 | - Parser – parser is like controller, it can modify data before storing, but cannot change response
52 | - Storing – after successful parsing, data from “content” property of result object is stored as content of cms document
53 |
54 | ## Front-end developers love it
55 | We don’t like auto-generated components like forms, menus, or carousels, because every front-end is unique, has own styles, layouts, javascript, etc... We want freedom when choosing what will be component looks like, what javascript framework will be used, and how will be data loaded (AJAX, or rendered on the server). Instead of build your own widgets directly in the administration area, and reuse them.
56 |
57 | ## Strong REST API
58 | Of course, API is very useful when we need to migrate content, run some scheduled tasks, or do something from outside. Every user has auto-generated API key. If you want to activate it, just define IPs from which requests are allowed, or simple type “*” to allow any IP. Then you can request all cms resources used by admin area path (/admin/…), of course, have to be allowed by the role of a user.
59 |
60 | ## Forms like you always wish
61 | Forms are something like simple data models. Instead of generating HTML forms, they are publishing API, which can be used as rest endpoint when calling from AJAX (e.g. POST JSON /cmsforms/formId), in old fashioned HTML form way with query parameters or hidden inputs, or from internal API as CmsForm model. If you want pair multiple forms data on some key, just decide which property will be pairing key. You can ask visitors questions in multiple steps, or later, and send different emails. Sending emails can be triggered when some form property is defined or updated, etc…
62 |
63 | ## Customize anything – thanks to total.js
64 | Total.js is full-featured, well designed, and very fast nodejs framework. You can extend or replace anything: serving static files, generating e-tags, authentication, authorization, controller behaviors, etc… It brings nice modularity approach. You can use modules, or whole packages to extend cms functionality.
65 |
66 | ## Business ready
67 | NodeJS is a serious platform. It really can replace some technologies used today. So, why stop with something like basic cms. Let’s make something ready for real business needs.
68 | Planned features:
69 | - Content editors roles, permissions, and workflow
70 | - Content versioning
71 | - Member areas (define non-public areas on website, which are enabled only for registered members)
72 | - Full-text search - sync with Elastic
73 |
74 |
75 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat
76 | [license-url]: license.txt
77 |
--------------------------------------------------------------------------------
/config-debug:
--------------------------------------------------------------------------------
1 | name : nodee-cms
2 | version : 1.0.0
3 |
4 | default-ip : 127.0.0.1
5 | default-port : 8080
6 |
7 | // static files max-age set to 2 minutes
8 | default-response-maxage : 120
9 |
10 | // session cookie name, expiration in hours
11 | session-cookie-name : __user
12 | session-cookie-secure : false
13 | session-cookie-httpOnly : true
14 | session-cookie-expires : 24
15 |
16 | // auth settings
17 | // auth-datasource-database : nodee-cms-db
18 | auth-datasource-collection : users
19 | auth-mailer-use : mailer-primary
20 | auth-mailer-subject : Password changed
21 | auth-mailer-forgotpass : emails/forgotpass
22 |
23 | // main datasource setings (if database missing app name will be used)
24 | // datasource-primary-host : localhost
25 | // datasource-primary-database : nodee-cms-db
26 | // datasource-primary-username : dbuser
27 | // datasource-primary-password : dbpass
28 |
29 | // primary mailer conf
30 | mailer-primary-from : your@email.com
31 | mailer-primary-name : Demo
32 | mailer-primary-host : smtp.gmail.com
33 | mailer-primary-port : 465
34 | mailer-primary-secure : true
35 | mailer-primary-user : your@email.com
36 | mailer-primary-password : supersecretpassword
37 |
38 | // admin base path, "/admin/" by default
39 | admin-base-path : /cmsadmin
--------------------------------------------------------------------------------
/config-release:
--------------------------------------------------------------------------------
1 | name : nodee-cms
2 | version : 1.0.0
3 |
4 | default-ip : 127.0.0.1
5 | default-port : 8080
6 |
7 | // static files max-age set to 2 minutes
8 | default-response-maxage : 120
9 |
10 | // session cookie name, expiration in hours
11 | session-cookie-name : __user
12 | session-cookie-secure : true
13 | session-cookie-httpOnly : true
14 | session-cookie-expires : 24
15 |
16 | // auth settings
17 | // auth-datasource-database : nodee-cms-db
18 | auth-datasource-collection : users
19 | auth-mailer-use : mailer-primary
20 | auth-mailer-subject : Password changed
21 | auth-mailer-forgotpass : emails/forgotpass
22 |
23 | // main datasource setings (if database missing app name will be used)
24 | // datasource-primary-host : localhost
25 | // datasource-primary-database : nodee-cms-db
26 | // datasource-primary-username : dbuser
27 | // datasource-primary-password : dbpass
28 |
29 | // primary mailer conf
30 | mailer-primary-from : your@email.com
31 | mailer-primary-name : Demo
32 | mailer-primary-host : smtp.gmail.com
33 | mailer-primary-port : 465
34 | mailer-primary-secure : true
35 | mailer-primary-user : your@email.com
36 | mailer-primary-password : supersecretpassword
37 |
38 | // admin base path, "/admin/" by default
39 | admin-base-path : /cmsadmin
--------------------------------------------------------------------------------
/debug.js:
--------------------------------------------------------------------------------
1 | var packages = [
2 | 'nodee-total',
3 | 'nodee-admin',
4 | 'nodee-cms'
5 | ];
6 |
7 | /*
8 | * Debug configs
9 | */
10 |
11 | var childProcessDebuggerPort = 5859,
12 | watchDirectories = [ '/controllers', '/definitions', '/modules', '/resources', '/components', '/models', '/source' ],
13 | watchExtensions = ['.js', '.resource', '.html'],
14 | watchFiles = ['config', 'config-debug', 'config-release', 'versions'];
15 |
16 | /*
17 | * DEBUG setup:
18 | */
19 | var isDebugging = process.argv[process.argv.length - 1] === 'debugging';
20 | var directory = process.cwd();
21 | var path = require('path');
22 | var fs = require('fs');
23 |
24 | // download all packages and start app
25 | if(!isDebugging) require('./packages.js').downloadPackages(packages, function(){
26 | require('total.js'); // init total.js
27 | run(); // run app in debug mode with file watchers
28 | console.warn('DOWNLOADING');
29 | });
30 | else {
31 | require('total.js'); // init total.js
32 | run(); // run app in debug mode with file watchers
33 | }
34 |
35 | function debug() {
36 | var options = {};
37 | var framework = require('total.js');
38 | var port = parseInt(process.argv[2]);
39 | if (options.https) return framework.https('debug', options);
40 | framework.http('debug', options);
41 | }
42 |
43 | function app() {
44 | var fork = require('child_process').fork;
45 | var directories = [];
46 | var files = {};
47 | var force = false;
48 | var changes = [];
49 | var app = null;
50 | var status = 0;
51 | var async = new utils.Async;
52 | var pid = '';
53 | var pidInterval = null;
54 | var prefix = '------------> ';
55 | var isLoaded = false;
56 |
57 | for(var i=0;i 0) {
181 | console.log(prefix + 'PID: ' + process.pid);
182 | pid = path.join(directory, 'debug.pid');
183 | fs.writeFileSync(pid, process.pid);
184 | pidInterval = setInterval(function() {
185 | fs.exists(pid, function(exist) {
186 | if (exist) return;
187 | fs.unlink(pid, noop);
188 | if (app !== null) process.kill(app.pid);
189 | process.exit(0);
190 | })
191 | }, 2e3);
192 | }
193 | restart();
194 | refresh_directory();
195 | }
196 |
197 | function run(){
198 | if(isDebugging) return debug();
199 | var filename = path.join(directory, 'debug.pid');
200 | if(!fs.existsSync(filename)) return app();
201 | fs.unlinkSync(filename);
202 | setTimeout(app, 3e3);
203 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodee-cms",
3 | "preferGlobal": false,
4 | "version": "1.0.1",
5 | "author": "NODEE CMS - Matus Szabo ",
6 | "description": "cms - content management system",
7 | "dependencies":{
8 | "total.js":"~2.7.0",
9 | "uglify-js":"~3.0.24",
10 | "nodee-utils":"*",
11 | "nodee-model":"*",
12 | "nodee-view":"*"
13 | },
14 | "analyze": false,
15 | "devDependencies": { },
16 | "bundledDependencies":[],
17 | "license": "MIT",
18 | "engines": {
19 | "node": ">=4.0.0"
20 | }
21 | }
--------------------------------------------------------------------------------
/packages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var http = require('http'),
4 | https = require('https'),
5 | nodeUrl = require('url'),
6 | fs = require('fs');
7 |
8 | // clear packages folder
9 | deleteFolderRecursiveSync('packages');
10 |
11 | // create packages folder
12 | fs.mkdirSync('packages');
13 |
14 | module.exports.downloadPackages = downloadPackages;
15 | module.exports.downloadFile = downloadFile;
16 | module.exports.deleteFolderRecursiveSync = deleteFolderRecursiveSync;
17 |
18 |
19 | function downloadPackages(packages, cb, i){
20 | i = i || 0;
21 |
22 | var baseUrl = 'https://packages.nodee.io/' +(packages.accessKey ? packages.accessKey+'/' : '')+ 'latest/';
23 | var fileStream = fs.createWriteStream('packages/' + packages[i] + '.package');
24 | var url = baseUrl + packages[i] + '.package';
25 |
26 | downloadFile(url, fileStream, function(err){
27 | if(err) throw err;
28 |
29 | var fileStream = fs.createWriteStream('packages/' + packages[i] + '.package.json');
30 | var url = baseUrl + packages[i] + '.package.json';
31 | downloadFile(url, fileStream, function(err){
32 | if(err) throw err;
33 | i++;
34 | if(i < packages.length) downloadPackages(packages, cb, i);
35 | else cb();
36 | });
37 | });
38 | }
39 |
40 | function downloadFile(url, fileStream, cb){ // cb(err)
41 | var agent = http;
42 | if(url.substring(0,5)==='https') agent = https;
43 |
44 | var requestOpts = nodeUrl.parse(url);
45 |
46 | // fake browser headers
47 | requestOpts.headers = {};
48 |
49 | agent.get(requestOpts, function(res){
50 | if([301,302].indexOf(res.statusCode) > -1 && res.headers.location){
51 | // follow redirect
52 | return downloadFile(res.headers.location, fileStream, cb);
53 | }
54 |
55 | res.pipe(fileStream);
56 | res.on('end', cb);
57 | });
58 | }
59 |
60 | function deleteFolderRecursiveSync(path) {
61 | if( fs.existsSync(path) ) {
62 | fs.readdirSync(path).forEach(function(file, index){
63 | var curPath = path + "/" + file;
64 | if(fs.lstatSync(curPath).isDirectory()) { // recurse
65 | deleteFolderRecursiveSync(curPath);
66 | } else { // delete file
67 | fs.unlinkSync(curPath);
68 | }
69 | });
70 | fs.rmdirSync(path);
71 | }
72 | }
--------------------------------------------------------------------------------
/release.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var packages = [
4 | 'nodee-total',
5 | 'nodee-admin',
6 | 'nodee-cms'
7 | ];
8 |
9 | // download all packages and start app
10 | require('./packages.js').downloadPackages(packages, function(){
11 | require('total.js').http('release'); // start app
12 | });
--------------------------------------------------------------------------------
/views/layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Nodee CMS
5 |
6 |
7 |
8 |
9 |