Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added error handling and private repos can now be viewed (but not aut…

…omatically recompiled yet)
  • Loading branch information...
commit a0c90f5ce17a4de768617cbc3ef112d6b9427323 1 parent 1070dba
@jeromegn authored
View
1  Makefile
@@ -6,6 +6,7 @@ test:
public/stylesheets:
stylus app/stylesheets/screen.styl -o public/stylesheets --use nib
stylus app/stylesheets/themes -o public/stylesheets/themes --use nib
+ stylus app/stylesheets/site.styl -o public/stylesheets --use nib
install:
npm install
View
55 app/models/project.coffee
@@ -16,6 +16,7 @@ class Project
ribbon: true
google_analytics: null
theme: null
+ private: false
@_normalizeName: (username, project_name)->
return "#{username}/#{project_name}".toLowerCase()
@@ -78,7 +79,7 @@ class Project
# Retrieve from the DB
- _retrieve: (callback)->
+ _retrieve: (access_token, callback)->
redis.hgetall @redisKey, (error, result)=>
return callback(error) if error
return callback(null, this) unless result
@@ -86,8 +87,14 @@ class Project
@[key] = value for key, value of @_parse(result)
catch error
callback(error)
- callback(null, this)
+ if @config.private
+ Github.authorize access_token, @username, @project_name, (error, authorized)=>
+ return callback(error) if error
+ return callback() unless authorized
+ callback(null, this)
+ else
+ callback(null, this)
_saveable: ->
return {
@@ -122,8 +129,8 @@ class Project
# Gets the readme and config from Github
- update: (callback)->
- github = new Github()
+ update: (access_token, callback)->
+ github = new Github(access_token)
Async.parallel
config: (done)=>
@@ -142,17 +149,27 @@ class Project
done(null, status: status, content: content)
readme: (done)=>
- github.get path: "repos/#{@name}/readme", (error, status, content)->
+ github.get path: "repos/#{@name}/readme", (error, status, content)=>
return done(error) if error
- return done(new Error("Can't fetch README for #{@name}")) if status != 200
- done(null, status: status, content: content)
+ switch status
+ when 404
+ done()
+ when 200
+ done(null, status: status, content: content)
+ else
+ done(new Error("Can't fetch README for #{@name}")) if status != 200
+
+
, (error, results)=>
return callback(error) if error
+ return callback() unless results.readme
try
config = JSON.parse(results.config.content)
catch error
config = {}
+ config.private = true if access_token
+
@config = config
@source = results.readme.content
@@ -161,21 +178,17 @@ class Project
@save callback
- @load: (username, project_name, callback)->
+ @load: (username, project_name, access_token, callback)->
project = new Project(username, project_name)
-
- if project._isComplete
- return callback(null, project)
- else
- project._retrieve (error, project)=>
- return callback(error) if error
- if !project._isComplete
- project.update (error)=>
- return callback(error) if error
- project.save callback
-
- else
- callback(null, project)
+
+ project._retrieve access_token, (error, project)=>
+ return callback(error) if error
+ return callback() unless project
+ if !project._isComplete
+ project.update access_token, callback
+
+ else
+ callback(null, project)
View
44 app/resources/projects.coffee
@@ -1,6 +1,7 @@
Server = require("../../server")
Project = require("../models/project")
Markdown = require("../../lib/markdown")
+Github = require("../../lib/github")
# Reusable regex to find the right files
file_matchers = require("../../lib/matchers")
@@ -74,7 +75,7 @@ Server.get "/compiled", compile_route
Server.get "/", (req, res, next)->
- Project.load "jeromegn", "DocumentUp", (error, project)->
+ Project.load "jeromegn", "DocumentUp", req.session.access_token, (error, project)->
render_project req, res, project
@@ -88,25 +89,42 @@ Server.get "/:username/:project_name", (req, res, next)->
return next() if req.params.username == "stylesheets" || req.params.username == "javascripts" || req.params.username == "images"
return res.redirect("/", 301) if req.params.username == "username" && req.params.project_name == "repository"
- Project.load req.params.username, req.params.project_name, (error, project)->
- return next(error) if error
- if (config = req.query.config && Project.makeConfig(JSON.parse(req.query.config))) && !Object.equal(project.config, config)
- project.config = config
- project.save (error, project)->
- return next(error) if error
+ if req.query.auth
+ return res.redirect Github.oauthUrl("/#{req.params.username}/#{req.params.project_name}")
+
+ if code = req.query.code
+ Github.getAccessToken code, (error, access_token)->
+ return next(error) if error
+ return next() unless access_token
+
+ req.session.access_token = access_token
+ return res.redirect "/#{req.params.username}/#{req.params.project_name}"
+ else
+ Project.load req.params.username, req.params.project_name, req.session.access_token, (error, project)->
+ return next(error) if error
+ return next() unless project
+ if (config = req.query.config && Project.makeConfig(JSON.parse(req.query.config))) && !Object.equal(project.config, config)
+ project.config = config
+ project.save (error, project)->
+ return next(error) if error
+ res.render "projects/show", locals: {project: project, theme: req.query.theme || project.config.theme}, (error, html)->
+ respond_with_html(res, html)
+
+ else
res.render "projects/show", locals: {project: project, theme: req.query.theme || project.config.theme}, (error, html)->
- respond_with_html(res, html)
+ respond_with_html(res, html)
- else
- res.render "projects/show", locals: {project: project, theme: req.query.theme || project.config.theme}, (error, html)->
- respond_with_html(res, html)
+Server.get "/:username/:project_name", (req, res, next)->
+ return next() if req.params.username == "stylesheets" || req.params.username == "javascripts" || req.params.username == "images"
+ res.render "projects/404", layout: "layouts/error", locals:
+ project: "#{req.params.username}/#{req.params.project_name}"
# Manual recompile
Server.get "/:username/:project_name/recompile", (req, res, next)->
- Project.load req.params.username, req.params.project_name, (error, project)->
+ Project.load req.params.username, req.params.project_name, req.session.access_token, (error, project)->
return next(error) if error
- project.update (error)->
+ project.update (req.session.access_token || null), (error)->
return next(error) if error
res.redirect "/#{project.name}", 302
View
21 app/stylesheets/base.styl
@@ -0,0 +1,21 @@
+@import "nib";
+@import "lib/normalize";
+
+html {
+ height: 100%;
+}
+
+body {
+ padding: 0;
+ margin: 0;
+ font: 14px/22px "adelle", Georgia, sans-serif;
+ font-size-adjust:none;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:normal;
+ background: #F4F6EC;
+}
+
+a {
+ color: #369;
+}
View
0  app/stylesheets/normalize.styl → app/stylesheets/lib/normalize.styl
File renamed without changes
View
36 app/stylesheets/screen.styl
@@ -1,27 +1,6 @@
-@import "nib";
-
-@import "normalize";
-
@import "syntax/tomorrow";
-html {
- height: 100%;
-}
-
-body {
- padding: 0;
- margin: 0;
- font: 14px/22px "adelle", Georgia, sans-serif;
- font-size-adjust:none;
- font-style:normal;
- font-variant:normal;
- font-weight:normal;
- background: #F4F6EC;
-}
-
-a {
- color: #369;
-}
+@import "base"
// Layout
@@ -108,11 +87,10 @@ a {
#content {
padding: 30px 30px 20px 30px;
min-height: 100px;
- max-width: 600px;
+ width: 600px;
background: #fff;
float: left;
border: 1px solid rgba(black, 0.2);
- border-bottom: 0;
border-radius: 3px 3px 0 0;
margin-top: 15px;
@@ -129,9 +107,12 @@ a {
font-size: 18px;
}
- p { padding:0 0 0.8125em 0; color:#444; clearfix();}
+ > p {
+ clearfix()
+ }
+ p { padding:0 0 0.8125em 0; color:#444;}
- p img { float: left; margin: 0.5em 0.8125em 0.8125em 0; padding: 0; }
+ p img { float: left; margin: 0.5em 0.8125em 0.8125em 0; padding: 0; }
img { max-width: 100%; }
@@ -143,11 +124,12 @@ a {
h4 { font-size: 1.1em; margin: 1.161em 0 0.4em; }
h5,h6 { font-size: 1em; font-weight: bold; margin: 1.238em 0 0.4em; }
+ > h1, > h2 {margin-top: 0}
ul{list-style-position:outside;}
li ul,
li ol { margin:0 1.625em; }
- ul, ol { margin: 0 0 1.625em 1em; }
+ ul, ol { margin: 0 0 1.625em 1.25em; }
dl { margin: 0 0 1.625em 0; }
View
20 app/stylesheets/site.styl
@@ -0,0 +1,20 @@
+@import "base";
+
+.error {
+ width: 300px;
+ padding: 30px;
+ margin: 100px auto 0;
+ text-align: center;
+ background: #fff;
+ border-radius: 3px;
+ border: 1px solid #CCCCCC;
+ p {
+ margin-bottom: 10px;
+ }
+}
+
+h1 {
+ font-size: 24px;
+ margin: 0 0 20px;
+ font-weight: bold;
+}
View
2  app/stylesheets/themes/v1.styl
@@ -1,6 +1,6 @@
@import "nib";
-@import "../normalize";
+@import "../lib/normalize";
@import "../syntax/tomorrow";
View
3  app/views/errors/404.eco
@@ -0,0 +1,3 @@
+<h1>Not found</h1>
+<p>Maybe there's a typo in there?</p>
+<a href="/">Return home</a>
View
3  app/views/errors/500.eco
@@ -0,0 +1,3 @@
+<h1>Server error</h1>
+<p>My bad. I've been notified, will fix asap!</p>
+<a href="/">Return home</a>
View
39 app/views/layouts/error.eco
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Error | DocumentUp</title>
+ <link rel="stylesheet" href="/stylesheets/site.css">
+ <% if @env == "production": %>
+ <!-- Google Analytics -->
+ <script type="text/javascript">
+
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-5201171-14']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+ <% end %>
+ <!-- Typekit -->
+ <% unless @env == "test": %>
+ <script type="text/javascript">
+ (function() {
+ var config = {
+ kitId: 'hjp0pft',
+ scriptTimeout: 3000
+ };
+ var h=document.getElementsByTagName("html")[0];h.className+=" wf-loading";var t=setTimeout(function(){h.className=h.className.replace(/( |^)wf-loading( |$)/g,"");h.className+=" wf-inactive"},config.scriptTimeout);var tk=document.createElement("script");tk.src='//use.typekit.net/'+config.kitId+'.js';tk.type="text/javascript";tk.async="true";tk.onload=tk.onreadystatechange=function(){var a=this.readyState;if(a&&a!="complete"&&a!="loaded")return;clearTimeout(t);try{Typekit.load(config)}catch(b){}};var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(tk,s)
+ })();
+ </script>
+ <% end %>
+ </head>
+ <body>
+ <div class="error">
+ <%- @body %>
+ </div>
+ </body>
+</html>
View
3  app/views/projects/404.eco
@@ -0,0 +1,3 @@
+<h1>Not found</h1>
+<p>Maybe there's a typo in there?<br>Or maybe this is a private repository?<br>If so, <a href="/<%= @project %>?auth=1">login</a>.</p>
+<a href="/">Return home</a>
View
32 lib/github.coffee
@@ -1,6 +1,8 @@
Request = require("request")
Async = require("async")
logger = require("../config/logger")
+config = require("../config")
+QS = require("qs")
file_matchers = require("./matchers")
@@ -12,7 +14,6 @@ class Github
constructor: (@accessToken)->
-
post: (params, callback)->
params = Object.clone(params)
params.method = "POST"
@@ -33,18 +34,19 @@ class Github
"Accept": "application/vnd.github.beta.raw+json"
# Got an access token kiddo?
- headers["Authorization"] = "token #{@accessToken}" if @accessToken
+ headers["Authorization"] = "bearer #{@accessToken}" if @accessToken
return headers
# Get a file's raw contents
_getRaw: (params, callback)->
- params.headers = @_headers()
+ #params.headers = @_headers()
params.method = "GET"
# Build URL
params.url = "#{GITHUB_RAW}/#{params.path}"
+ params.url += "?access_token=#{@accessToken}" if @accessToken
delete params.path
logger.info "#{params.method} #{params.url}"
@@ -74,6 +76,7 @@ class Github
# Build URL
params.url = "#{GITHUB_API}/#{params.path}"
+ #params.url += "?access_token=#{@accessToken}" if @accessToken
delete params.path
logger.info "#{params.method} #{params.url}"
@@ -98,5 +101,28 @@ class Github
process.nextTick ->
callback error
+ @oauthUrl: (redirect_path)->
+ "https://github.com/login/oauth/authorize?client_id=#{config.github.client_id}&scope=user,repo&redirect_uri=#{encodeURIComponent(config.base_uri+redirect_path)}"
+
+ @getAccessToken: (code, callback)->
+ opts =
+ uri: "https://github.com/login/oauth/access_token?client_id=#{config.github.client_id}&client_secret=#{config.github.client_secret}&code=#{code}"
+ method: "POST"
+ Request opts, (error, resp, body)->
+ return callback(error) if error
+ try
+ if access_token = QS.parse(body).access_token
+ callback(null, access_token)
+ else
+ callback()
+ catch error
+ callback(error)
+
+ @authorize: (access_token, username, repository, callback)->
+ github = new Github(access_token)
+ github.get path: "repos/#{username}/#{repository}", (error, status, content)->
+ return callback(error) if error
+ return callback(null, false) unless status == 200
+ callback(null, true)
module.exports = Github
View
16 server.coffee
@@ -47,7 +47,7 @@ server.configure ->
server.use logger.middleware()
server.use Express.session(secret: "c96dbcc746d551ea0665da4a23536280", store: new RedisStore)
-
+
# Templates and views
server.set "views", "#{__dirname}/app/views"
server.set "view engine", "eco"
@@ -73,21 +73,25 @@ server.configure "development", ->
compile: compileStylus
server.use server.router
-
server.use Express.static "#{__dirname}/public"
server.configure "production", ->
-
server.error Express.errorHandler()
-
server.use Express.responseTime()
-
server.use server.router
-
server.use Express.static "#{__dirname}/public", maxAge: 1000 * 60 * 60 * 24 * 14
+server.configure ->
+ server.use (error, req, res, next)->
+ logger.error(error)
+ res.render "errors/500", layout: "layouts/error", status: 500
+
+ # 404 error
+ server.use (req, res, next)->
+ res.render "errors/404", layout: "layouts/error", status: 404
+
server.on "listening", ->
require("./app/resources/users")
require("./app/resources/projects")
Please sign in to comment.
Something went wrong with that request. Please try again.