Skip to content

Commit

Permalink
First import
Browse files Browse the repository at this point in the history
  • Loading branch information
claudioc committed Jan 6, 2013
0 parents commit b398897
Show file tree
Hide file tree
Showing 95 changed files with 12,628 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/*
config.yml
data/*
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Claudio Cicali <[email protected]>
http://ccl.me
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(The MIT License)

Copyright (c) 2012 Claudio Cicali <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Jiky
====

A simple git based wiki engine written for Node.js
The aim of wiki is to provide a very easy way to create a centralized documentation area for people used to work with git and markdown. It should fit well into a development team without the need to learn or install ad-hoc servers or applications. Jiky is very much inspired by the github wiki system (gollum), but tries to be more a stand-alone system than gollum is.

Features
--------

- No database: uses a git repository as the document archive
- No user management: authentication provided (only) via with Google logins
- Markdown for everything, [github flavored](http://github.github.com/github-flavored-markdown/)
- Uses [Markitup](http://markitup.jaysalvat.com/home/) as the markup editor, with a nice preview
- Inspired by the well known github [Gollum](https://github.com/github/gollum) wiki
- Show differences between document revisions
- Search through the content and the page names
- Layout accepts custom sidebar and footer
- Can also use custom css and JavaScript scripts
- White list for authorization on page reading and writing

Known limitations
-----------------

- There is only one authentication method (Google)
- The repository is "flat" (no directory)
- Authorization is only based on a regexp built white list on the user email address

Installation
------------

`npm install jiky`

Jiky needs a config file. To create a sample config file, just run `jiky -s`, redirect the output on a file and then edit it. The config file contains all the available configuration keys. Be sure to provide a valid server hostname (like wiki.mycompany.com) for Google Auth to be able to get back to you.

The basic command to run the wiki will then be

`jiky -c config.yaml`

Before running jiky you need to initialize its git repository somewhere (`git init` is sufficient).

Authentication
--------------

The _authentication_ section of the config file has two keys: anonRead and validMatches. If the anonRead is true, then anyone can read anything. If anonRead is false, then the email of the user MUST match at least one of the regular expressions provided via validMatches, which is a comma separated list. There is no "anonWrite", though. To edit a page the user must be authenticated.

The authentication is mandatory to edit pages from the web interface, but Jiky works on a git repository; that means that you could skip the authentication altogheter and edit pages with your editor and push to the remote that Jiky is serving.

Customization
-------------

You can customize Jiky in four different ways:

- add a left sidebar to every page: just add a file named `_sidebar.md` containing the markdown you want to display to the repository. You can edit or create the sidebar from Jiky itself, visiting `/wiki/_sidebar` (note that the title of the page in this case is useless)
- add a footer to every page: the page you need to create is "_footer.md" and the same rules for the sidebar apply
- add a custom CSS file, included in every page after every other CSS. The name of the file must be `_style.css` and must reside in the repository. It is not possible to edit the file from jiky itself
- add a custom JavaScript file, included in every page after every other JavaScript file. The name of the file must be `_script.js` and must reside in the repository. It is not possible to edit the file from jiky itself

All those files are cached (thus, not re-read for every page load, but kept in memory). This means that for every modification in _style.css and _script.js you need to restart the server. This is not tru for the footer and the sidebar ONLY if you edit those pages from jiky (which in that case will clear the cache by itself).

Jiky uses twitter Bootstrap and jQuery as its front-end components.
1 change: 1 addition & 0 deletions data
Submodule data added at 787c19
275 changes: 275 additions & 0 deletions jiky
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#!/usr/bin/env node

var express = require('express')
, http = require('http')
, path = require('path')
, passport = require('passport')
, Git = require("./lib/gitmech")
, expValidator = require('express-validator')
, gravatar = require("gravatar")
, Fs = require("fs")
, Url = require("url")
, Marked = require('marked')
, GoogleStrategy = require('passport-google').Strategy
, yaml = require("yaml")
, program = require('commander');

program.version('0.1.0')
.option('-c, --config <path>', 'Specify the config file')
.option('-s, --sample-config', 'Dumps a config file template and exits')
.parse(process.argv);

if (program.sampleConfig) {
console.log(sampleConfig());
process.exit(0);
}

if (!program.config || !Fs.existsSync(program.config)) {
program.help();
process.exit(-1);
}

var config = yaml.eval(Fs.readFileSync(program.config).toString());

if (!config.application || !config.server) {
console.log("A problem exists on the config file. Cannot continue.");
process.exit(-1);
}

var repo = config.application.repository;

try {
Git.init(repo);
} catch(e) {
console.log(e.message)
console.log("Please specify a valid git repository");
program.help()
process.exit(-1);
}

// Global to be accessed from the routes module
global.app = express();
app.locals.Git = Git;
app.locals.repo = repo;
app.locals._sidebar;
app.locals._footer;
app.locals._style;
app.locals._script;
app.locals.appTitle = config.application.title || "Jiky";
app.locals.port = config.server.port || process.env.PORT || 6067;
app.locals.hostname = config.server.hostname || "localhost";
app.locals.authorization = config.authorization || { anonRead: false, validMatches: ".+" };

app.locals.coalesce = function(value, def) {
return typeof value === 'undefined' ? def : value;
}

var routes = require("./routes");

app.configure('development', function() {
app.use(express.errorHandler());
app.locals.pretty = true; // Pretty HTML output from Jade
});

app.configure('production', function() {
});

app.configure(function() {
app.set('port', app.locals.port);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.logger('default'));
app.use(express.cookieParser("mille1000"));
app.use(express.cookieSession({ secret: 'tobo!', cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 }})); // a Month
app.use(express.bodyParser());
app.use(expValidator);
app.use(express.methodOverride());
app.use(passport.initialize());
app.use(passport.session());
app.use(function (req, res, next) {
res.locals({
get user() {
return req.user;
},
isAnonymous: function () {
return !req.user;
},
_sidebar: null,
_footer: null,
_style: null,
_script: null,
gravatar: function(email) {
return gravatar;
},
// FIXME: we need to handle these has* better, because we're hitting the
// fs for every single request even if we cache the sidebar/footer/etc
hasSidebar: function() {
return Fs.existsSync(app.locals.repo + "/" + "_sidebar.md");
},
hasFooter: function() {
return Fs.existsSync(app.locals.repo + "/" + "_footer.md");
},
hasCustomStyle: function() {
return Fs.existsSync(app.locals.repo + "/" + "_style.css");
},
hasCustomScript: function() {
return Fs.existsSync(app.locals.repo + "/" + "_script.js");
}
});

next();
});

// Logic to include custom _footer, _sidebar, _script and _style.css
app.use(function (req, res, next) {

if (res.locals.hasSidebar()) {
if (!app.locals._sidebar) {
Git.readFile("_sidebar.md", "HEAD", function(err, content) {
if (!err) {
res.locals._sidebar = app.locals._sidebar = Marked(content.split("\n").splice(1).join("\n"));
}
});
} else {
res.locals._sidebar = app.locals._sidebar;
}
} else {
res.locals._sidebar = app.locals._sidebar = null;
}

if (res.locals.hasFooter()) {
if (!app.locals._footer) {
Git.readFile("_footer.md", "HEAD", function(err, content) {
if (!err) {
res.locals._footer = app.locals._footer = Marked(content.split("\n").splice(1).join("\n"));
}
});
} else {
res.locals._footer = app.locals._footer;
}
} else {
res.locals._footer = app.locals._footer = null;
}

if (res.locals.hasCustomStyle()) {
if (!app.locals._style) {
// Read sync because this info is needed by the layout
res.locals._style = app.locals._style = Fs.readFileSync(app.locals.repo + "/" + "_style.css");
} else {
res.locals._style = app.locals._style;
}
} else {
res.locals._style = app.locals._style = null;
}

if (res.locals.hasCustomScript()) {
if (!app.locals._script) {
// Read sync because this info is needed by the layout
res.locals._script = app.locals._script = Fs.readFileSync(app.locals.repo + "/" + "_script.js");
} else {
res.locals._script = app.locals._script;
}
} else {
res.locals._script = app.locals._script = null;
}

next();
});

app.use(app.router);
});

function requireAuthentication(req, res, next) {
if (!res.locals.user) {
res.redirect("/login");
/*
res.statusCode = 403;
res.end('<h1>Forbidden</h1>');
*/
} else {
next();
}
}

/*
* Passport configuration
*/

passport.use(new GoogleStrategy({
returnURL: 'http://' + app.locals.hostname + ':' + app.locals.port + '/auth/google/return',
realm: 'http://' + app.locals.hostname + ':' + app.locals.port
},

function(identifier, profile, done) {
done(undefined, profile);
}
));

passport.serializeUser(function(user, done) {
done(null, user);
});

passport.deserializeUser(function(user, done) {
user.asGitAuthor = user.displayName + " <" + user.emails[0].value + ">";
done(undefined, user);
});

app.all("/pages/*", requireAuthentication);

if (!app.locals.authorization.anonRead) {
app.all("/wiki/*", requireAuthentication);
app.all("/search", requireAuthentication);
}

app.get ("/", routes.index);
app.get ("/wiki", routes.pageList);
app.get ("/wiki/:page", routes.pageShow);
app.get ("/wiki/:page/history", routes.pageHistory);
app.get ("/wiki/:page/:version", routes.pageShow);
app.get ("/wiki/:page/compare/:revisions", routes.pageCompare);

app.get ("/search", routes.pageSearch);

app.get ("/pages/new", routes.pageNew);
app.get ("/pages/new/:page", routes.pageNew);
app.post ("/pages", routes.pageCreate);

app.get ("/pages/:page/edit", routes.pageEdit);
app.put ("/pages/:page", routes.pageUpdate);
app.delete ("/pages/:page", routes.pageDestroy);

app.post ("/misc/preview", routes.miscPreview);
app.get ("/misc/syntax-reference", routes.miscSyntaxReference);
app.get ("/misc/existence", routes.miscExistence);

app.get ("/login", routes.login);
app.get ("/logout", routes.logout);

app.get ("/auth/google", passport.authenticate('google'));
app.get ("/auth/google/return", passport.authenticate('google', { successRedirect: '/auth/done', failureRedirect: '/login' }));
app.get ("/auth/done", routes.authDone);

app.all('*', routes.error404);

http.createServer(app).listen(app.get('port'), function(){
console.log((new Date()) + " - Jiky server listening on port " + app.get('port'));
});

// Dumps a sample config file
function sampleConfig() {
return "\
---\n\
# Configuration sample file for Jiky (YAML)\n\
application:\n\
repository: \"/home/user/test\"\n\
title: \"Jiky\"\n\
server:\n\
hostname: \"jiky.org\"\n\
port: 6067\n\
authorization:\n\
anonRead: true\n\
validMatches: \".+\"\
";
}
Loading

0 comments on commit b398897

Please sign in to comment.