wiki 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/wiki.rb +2 -0
- data/lib/wiki/ReadMe.md +89 -0
- data/lib/wiki/config.ru +2 -0
- data/lib/wiki/favicon.rb +31 -0
- data/lib/wiki/page.rb +74 -0
- data/lib/wiki/random_id.rb +5 -0
- data/lib/wiki/server.rb +336 -0
- data/lib/wiki/server_helpers.rb +66 -0
- data/lib/wiki/stores/ReadMe.md +26 -0
- data/lib/wiki/stores/all.rb +3 -0
- data/lib/wiki/stores/couch.rb +121 -0
- data/lib/wiki/stores/file.rb +53 -0
- data/lib/wiki/stores/store.rb +38 -0
- data/lib/wiki/version.rb +3 -0
- data/lib/wiki/views/client/Gruntfile.js +50 -0
- data/lib/wiki/views/client/ReadMe.md +67 -0
- data/lib/wiki/views/client/build-test.bat +10 -0
- data/lib/wiki/views/client/build.bat +8 -0
- data/lib/wiki/views/client/builder.pl +41 -0
- data/lib/wiki/views/client/client.coffee +3 -0
- data/lib/wiki/views/client/client.js +3607 -0
- data/lib/wiki/views/client/crosses.png +0 -0
- data/lib/wiki/views/client/images/external-link-ltr-icon.png +0 -0
- data/lib/wiki/views/client/images/noise.png +0 -0
- data/lib/wiki/views/client/images/oops.jpg +0 -0
- data/lib/wiki/views/client/js/d3/d3.behavior.js +198 -0
- data/lib/wiki/views/client/js/d3/d3.chart.js +984 -0
- data/lib/wiki/views/client/js/d3/d3.csv.js +92 -0
- data/lib/wiki/views/client/js/d3/d3.geo.js +566 -0
- data/lib/wiki/views/client/js/d3/d3.geom.js +825 -0
- data/lib/wiki/views/client/js/d3/d3.js +3597 -0
- data/lib/wiki/views/client/js/d3/d3.layout.js +1923 -0
- data/lib/wiki/views/client/js/d3/d3.time.js +660 -0
- data/lib/wiki/views/client/js/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/wiki/views/client/js/images/ui-icons_222222_256x240.png +0 -0
- data/lib/wiki/views/client/js/jquery-1.6.2.min.js +18 -0
- data/lib/wiki/views/client/js/jquery-1.7.1.min.js +4 -0
- data/lib/wiki/views/client/js/jquery-1.9.1.min.js +5 -0
- data/lib/wiki/views/client/js/jquery-migrate-1.1.1.min.js +3 -0
- data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.css +5 -0
- data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.js +6 -0
- data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.css +339 -0
- data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.min.js +315 -0
- data/lib/wiki/views/client/js/jquery.ie.cors.js +310 -0
- data/lib/wiki/views/client/js/jquery.ui.touch-punch.min.js +11 -0
- data/lib/wiki/views/client/js/modernizr.custom.63710.js +824 -0
- data/lib/wiki/views/client/js/sockjs-0.3.min.js +27 -0
- data/lib/wiki/views/client/js/underscore-min.js +30 -0
- data/lib/wiki/views/client/mkplugin.sh +97 -0
- data/lib/wiki/views/client/package.json +36 -0
- data/lib/wiki/views/client/runtests.html +26 -0
- data/lib/wiki/views/client/style.css +339 -0
- data/lib/wiki/views/client/test/mocha.css +231 -0
- data/lib/wiki/views/client/test/mocha.js +5340 -0
- data/lib/wiki/views/client/test/testclient.js +17133 -0
- data/lib/wiki/views/client/testclient.coffee +18 -0
- data/lib/wiki/views/client/theme/granite.css +59 -0
- data/lib/wiki/views/client/theme/stoneSeamless.jpg +0 -0
- data/lib/wiki/views/client/twitter-maintainance.jpg +0 -0
- data/lib/wiki/views/layout.haml +56 -0
- data/lib/wiki/views/oops.haml +5 -0
- data/lib/wiki/views/page.haml +20 -0
- data/lib/wiki/views/static.html +30 -0
- data/lib/wiki/views/view.haml +2 -0
- data/wiki.gemspec +28 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: af6c3920f6679b9844dd249d0c5cc3fd61982799
|
4
|
+
data.tar.gz: 4d5247d3d0876a2ae044742b217e74b796e87f96
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c818d43b7dfeb8e1d2cbcb720fd00a44b7df4784fb82aafd695fef6ffbafa4098d386d7ae872b6a1a76e83bd67afa22de721345455344cec6175848cbf61474
|
7
|
+
data.tar.gz: afffe40b5c4488773fa46abf1a602752b49e976e1f1b7d878a96e1935d5623c6cff612dc86ed8753721c24a3bc0cb68cd76ded290642c6fd9799f5bf89cedd14
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Daniel Stark
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Wiki
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'wiki'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install wiki
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/wiki.rb
ADDED
data/lib/wiki/ReadMe.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
Server Goals
|
2
|
+
============
|
3
|
+
|
4
|
+
The server participates in a peer-to-peer exchange of page content and page metadata.
|
5
|
+
It is expected to be mostly-on so that it can support the needs of peers and anticipate the needs of ux clients of this server.
|
6
|
+
In summary, the server's peer-to-peer side exists to:
|
7
|
+
|
8
|
+
* Encourage the deployment of independently owned content stores.
|
9
|
+
* Support community among owners through systematic sharing of content.
|
10
|
+
|
11
|
+
|
12
|
+
Customizing your Server
|
13
|
+
=======================
|
14
|
+
|
15
|
+
The distribution contains default files. They will be copied the first time
|
16
|
+
they're requested, if you don't install your own. These are:
|
17
|
+
|
18
|
+
default-data/pages/welcome-visitors
|
19
|
+
default-data/status/favicon.png (unused, see below)
|
20
|
+
|
21
|
+
The first is the usual welcome page offered as the server's home page.
|
22
|
+
The second is a 32x32 png gradient that is used to identify your server
|
23
|
+
in bookmarks, browser tabs, page headings and journal entries.
|
24
|
+
|
25
|
+
You can revise the welcome page by editing your copy here:
|
26
|
+
|
27
|
+
data/pages/welcome-visitors
|
28
|
+
|
29
|
+
A suitable random gradient will be generated for you.
|
30
|
+
You can remove or replace it here:
|
31
|
+
|
32
|
+
data/status/favicon.png
|
33
|
+
|
34
|
+
|
35
|
+
Launching the Server
|
36
|
+
====================
|
37
|
+
|
38
|
+
We're now using Ruby 1.9.2 which we manage with rvm. Launch the server with the following bundler commands:
|
39
|
+
|
40
|
+
rvm 1.9.2
|
41
|
+
bundle exec rackup -s thin -p 1111
|
42
|
+
|
43
|
+
Hosting a Server Farm
|
44
|
+
=====================
|
45
|
+
|
46
|
+
The server can host separate pages and status directories for a number of virtual hosts. Enable this by creating the subdirectory:
|
47
|
+
|
48
|
+
data/farm
|
49
|
+
|
50
|
+
or by setting the environment variable
|
51
|
+
|
52
|
+
FARM_MODE=true
|
53
|
+
|
54
|
+
The server will create subdirectories with farm for each virtual host name and locate pages and status directories within that.
|
55
|
+
|
56
|
+
Recursive Server Calls
|
57
|
+
======================
|
58
|
+
|
59
|
+
Federated sites hosted in the same farm can cause recursive web requests.
|
60
|
+
This is an issue for certain rack servers, notably thin, which is widely used in production rack setups.
|
61
|
+
If you have a standard server configuration, in which all traffic coming to *.my-sfw-farm.org will be handled by
|
62
|
+
a single server, you can serve page json and favicons in the context of the current request
|
63
|
+
(instead of generating an additional HTTP request)
|
64
|
+
by setting the environment variable:
|
65
|
+
|
66
|
+
FARM_DOMAINS=my-sfw-farm.org
|
67
|
+
|
68
|
+
Your server can handle multiple domains by comma-separating them:
|
69
|
+
|
70
|
+
FARM_DOMAINS=my-sfw-farm.org,fedwiki.jacksmith.com
|
71
|
+
|
72
|
+
With this setup, pages and favicons will be served more efficiently, as well as being friendly to single-threaded servers like thin.
|
73
|
+
|
74
|
+
Alternately, you can use webrick, which handles recursive calls out of the box. Launch it with this command:
|
75
|
+
|
76
|
+
bundle exec rackup -s webrick -p 1111
|
77
|
+
|
78
|
+
CouchDB
|
79
|
+
=======
|
80
|
+
|
81
|
+
By default, all pages, favicons, and server claims are stored in the server's local filesystem.
|
82
|
+
If you prefer to use CouchDB for storage, set two environment variables:
|
83
|
+
|
84
|
+
STORE_TYPE=CouchStore
|
85
|
+
COUCHDB_URL=https://username:password@some-couchdb-host.com
|
86
|
+
|
87
|
+
If you want to run a farm with CouchDB, be sure to set the environment variable
|
88
|
+
|
89
|
+
FARM_MODE=true
|
data/lib/wiki/config.ru
ADDED
data/lib/wiki/favicon.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'png'
|
3
|
+
|
4
|
+
class Favicon
|
5
|
+
class << self
|
6
|
+
def create_blob
|
7
|
+
canvas = PNG::Canvas.new 32, 32
|
8
|
+
light = PNG::Color.from_hsv(256*rand,200,255).rgb()
|
9
|
+
dark = PNG::Color.from_hsv(256*rand,200,125).rgb()
|
10
|
+
angle = 2 * (rand()-0.5)
|
11
|
+
sin = Math.sin angle
|
12
|
+
cos = Math.cos angle
|
13
|
+
scale = sin.abs + cos.abs
|
14
|
+
for x in (0..31)
|
15
|
+
for y in (0..31)
|
16
|
+
p = (sin >= 0 ? sin*x+cos*y : -sin*(31-x)+cos*y) / 31 / scale
|
17
|
+
canvas[x,y] = PNG::Color.new(
|
18
|
+
light[0]*p + dark[0]*(1-p),
|
19
|
+
light[1]*p + dark[1]*(1-p),
|
20
|
+
light[2]*p + dark[2]*(1-p))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
PNG.new(canvas).to_blob
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_or_create(path)
|
27
|
+
Store.get_blob(path) || Store.put_blob(path, Favicon.create_blob)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/wiki/page.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
require File.expand_path("../random_id", __FILE__)
|
3
|
+
require File.expand_path("../stores/all", __FILE__)
|
4
|
+
|
5
|
+
class PageError < StandardError; end;
|
6
|
+
|
7
|
+
# Page Class
|
8
|
+
# Handles writing and reading JSON data to and from files.
|
9
|
+
class Page
|
10
|
+
|
11
|
+
# Directory where pages are to be stored.
|
12
|
+
attr_accessor :directory
|
13
|
+
# Directory where default (pre-existing) pages are stored.
|
14
|
+
attr_accessor :default_directory
|
15
|
+
# Directory where plugins that may have pages are stored.
|
16
|
+
attr_accessor :plugins_directory
|
17
|
+
|
18
|
+
def plugin_page_path name
|
19
|
+
Dir.glob(File.join(plugins_directory, '*/pages')) do |dir|
|
20
|
+
probe = "#{dir}/#{name}"
|
21
|
+
return probe if File.exists? probe
|
22
|
+
end
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Get a page
|
28
|
+
#
|
29
|
+
# @param [String] name - The name of the file to retrieve, relative to Page.directory.
|
30
|
+
# @return [Hash] The contents of the retrieved page (parsed JSON).
|
31
|
+
def get(name)
|
32
|
+
assert_attributes_set
|
33
|
+
path = File.join(directory, name)
|
34
|
+
default_path = File.join(default_directory, name)
|
35
|
+
page = Store.get_page(path)
|
36
|
+
if page
|
37
|
+
page
|
38
|
+
elsif File.exist?(default_path)
|
39
|
+
FileStore.get_page(default_path)
|
40
|
+
elsif (path = plugin_page_path name)
|
41
|
+
page = FileStore.get_page(path)
|
42
|
+
page['plugin'] = path.match(/plugins\/(.*?)\/pages/)[1]
|
43
|
+
page
|
44
|
+
else
|
45
|
+
halt 404
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def exists?(name)
|
50
|
+
Store.exists?(File.join(directory, name)) or
|
51
|
+
File.exist?(File.join(default_directory, name)) or
|
52
|
+
!plugin_page_path(name).nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create or update a page
|
56
|
+
#
|
57
|
+
# @param [String] name - The name of the file to create/update, relative to Page.directory.
|
58
|
+
# @param [Hash] page - The page data to be written to the file (it will be converted to JSON).
|
59
|
+
# @return [Hash] The contents of the retrieved page (parsed JSON).
|
60
|
+
def put(name, page)
|
61
|
+
assert_attributes_set
|
62
|
+
path = File.join directory, name
|
63
|
+
page.delete 'plugin'
|
64
|
+
Store.put_page(path, page, :name => name, :directory => directory)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def assert_attributes_set
|
70
|
+
raise PageError.new('Page.directory must be set') unless directory
|
71
|
+
raise PageError.new('Page.default_directory must be set') unless default_directory
|
72
|
+
raise PageError.new('Page.plugins_directory must be set') unless plugins_directory
|
73
|
+
end
|
74
|
+
end
|
data/lib/wiki/server.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'pathname'
|
4
|
+
Bundler.require
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
SINATRA_ROOT = File.expand_path(File.dirname(__FILE__))
|
8
|
+
APP_ROOT = File.expand_path(File.join(SINATRA_ROOT, "..", ".."))
|
9
|
+
|
10
|
+
Encoding.default_external = Encoding::UTF_8
|
11
|
+
|
12
|
+
require 'server_helpers'
|
13
|
+
require 'stores/all'
|
14
|
+
require 'random_id'
|
15
|
+
require 'page'
|
16
|
+
require 'favicon'
|
17
|
+
|
18
|
+
require 'openid'
|
19
|
+
require 'openid/store/filesystem'
|
20
|
+
|
21
|
+
class Controller < Sinatra::Base
|
22
|
+
set :port, 1111
|
23
|
+
set :public, File.join(APP_ROOT, "client")
|
24
|
+
set :views , File.join(SINATRA_ROOT, "views")
|
25
|
+
set :haml, :format => :html5
|
26
|
+
set :versions, `git log -10 --oneline` || "no git log"
|
27
|
+
if ENV.include?('SESSION_STORE')
|
28
|
+
use ENV['SESSION_STORE'].split('::').inject(Object) { |mod, const| mod.const_get(const) }
|
29
|
+
else
|
30
|
+
enable :sessions
|
31
|
+
end
|
32
|
+
helpers ServerHelpers
|
33
|
+
|
34
|
+
Store.set ENV['STORE_TYPE'], APP_ROOT
|
35
|
+
|
36
|
+
class << self # overridden in test
|
37
|
+
def data_root
|
38
|
+
File.join APP_ROOT, "data"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def farm_page(site=request.host)
|
43
|
+
page = Page.new
|
44
|
+
page.directory = File.join data_dir(site), "pages"
|
45
|
+
page.default_directory = File.join APP_ROOT, "default-data", "pages"
|
46
|
+
page.plugins_directory = File.join APP_ROOT, "client", "plugins"
|
47
|
+
Store.mkdir page.directory
|
48
|
+
page
|
49
|
+
end
|
50
|
+
|
51
|
+
def farm_status(site=request.host)
|
52
|
+
status = File.join data_dir(site), "status"
|
53
|
+
Store.mkdir status
|
54
|
+
status
|
55
|
+
end
|
56
|
+
|
57
|
+
def data_dir(site)
|
58
|
+
Store.farm?(self.class.data_root) ? File.join(self.class.data_root, "farm", site) : self.class.data_root
|
59
|
+
end
|
60
|
+
|
61
|
+
def identity
|
62
|
+
default_path = File.join APP_ROOT, "default-data", "status", "local-identity"
|
63
|
+
real_path = File.join farm_status, "local-identity"
|
64
|
+
id_data = Store.get_hash real_path
|
65
|
+
id_data ||= Store.put_hash(real_path, FileStore.get_hash(default_path))
|
66
|
+
end
|
67
|
+
|
68
|
+
post "/logout" do
|
69
|
+
session.delete :authenticated
|
70
|
+
redirect "/"
|
71
|
+
end
|
72
|
+
|
73
|
+
post '/login' do
|
74
|
+
begin
|
75
|
+
root_url = request.url.match(/(^.*\/{2}[^\/]*)/)[1]
|
76
|
+
identifier_file = File.join farm_status, "open_id.identifier"
|
77
|
+
identifier = Store.get_text(identifier_file)
|
78
|
+
unless identifier
|
79
|
+
identifier = params[:identifier]
|
80
|
+
end
|
81
|
+
open_id_request = openid_consumer.begin(identifier)
|
82
|
+
|
83
|
+
redirect open_id_request.redirect_url(root_url, root_url + "/login/openid/complete")
|
84
|
+
rescue
|
85
|
+
oops 400, "Trouble starting OpenID<br>Did you enter a proper endpoint?"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
get '/login/openid/complete' do
|
90
|
+
begin
|
91
|
+
response = openid_consumer.complete(params, request.url)
|
92
|
+
case response.status
|
93
|
+
when OpenID::Consumer::FAILURE
|
94
|
+
oops 401, "Login failure"
|
95
|
+
when OpenID::Consumer::SETUP_NEEDED
|
96
|
+
oops 400, "Setup needed"
|
97
|
+
when OpenID::Consumer::CANCEL
|
98
|
+
oops 400, "Login cancelled"
|
99
|
+
when OpenID::Consumer::SUCCESS
|
100
|
+
id = params['openid.identity']
|
101
|
+
id_file = File.join farm_status, "open_id.identity"
|
102
|
+
stored_id = Store.get_text(id_file)
|
103
|
+
if stored_id
|
104
|
+
if stored_id == id
|
105
|
+
# login successful
|
106
|
+
authenticate!
|
107
|
+
else
|
108
|
+
oops 403, "This is not your wiki"
|
109
|
+
end
|
110
|
+
else
|
111
|
+
Store.put_text id_file, id
|
112
|
+
# claim successful
|
113
|
+
authenticate!
|
114
|
+
end
|
115
|
+
else
|
116
|
+
oops 400, "Trouble with OpenID"
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
oops 400, "Trouble running OpenID<br>Did you enter a proper endpoint?"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
get '/system/slugs.json' do
|
124
|
+
content_type 'application/json'
|
125
|
+
cross_origin
|
126
|
+
JSON.pretty_generate(Dir.entries(farm_page.directory).reject{|e|e[0] == '.'})
|
127
|
+
end
|
128
|
+
|
129
|
+
get '/favicon.png' do
|
130
|
+
content_type 'image/png'
|
131
|
+
headers 'Cache-Control' => "max-age=3600"
|
132
|
+
cross_origin
|
133
|
+
Favicon.get_or_create(File.join farm_status, 'favicon.png')
|
134
|
+
end
|
135
|
+
|
136
|
+
get '/random.png' do
|
137
|
+
unless authenticated? or (!identified? and !claimed?)
|
138
|
+
halt 403
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
content_type 'image/png'
|
143
|
+
path = File.join farm_status, 'favicon.png'
|
144
|
+
Store.put_blob path, Favicon.create_blob
|
145
|
+
end
|
146
|
+
|
147
|
+
get '/' do
|
148
|
+
redirect "/#{identity['root']}.html"
|
149
|
+
end
|
150
|
+
|
151
|
+
get %r{^/data/([\w -]+)$} do |search|
|
152
|
+
content_type 'application/json'
|
153
|
+
cross_origin
|
154
|
+
pages = Store.annotated_pages farm_page.directory
|
155
|
+
candidates = pages.select do |page|
|
156
|
+
datasets = page['story'].select do |item|
|
157
|
+
item['type']=='data' && item['text'] && item['text'].index(search)
|
158
|
+
end
|
159
|
+
datasets.length > 0
|
160
|
+
end
|
161
|
+
halt 404 unless candidates.length > 0
|
162
|
+
JSON.pretty_generate(candidates.first)
|
163
|
+
end
|
164
|
+
|
165
|
+
get %r{^/([a-z0-9-]+)\.html$} do |name|
|
166
|
+
halt 404 unless farm_page.exists?(name)
|
167
|
+
haml :page, :locals => { :page => farm_page.get(name), :page_name => name }
|
168
|
+
end
|
169
|
+
|
170
|
+
get %r{^((/[a-zA-Z0-9:.-]+/[a-z0-9-]+(_rev\d+)?)+)$} do
|
171
|
+
elements = params[:captures].first.split('/')
|
172
|
+
pages = []
|
173
|
+
elements.shift
|
174
|
+
while (site = elements.shift) && (id = elements.shift)
|
175
|
+
if site == 'view'
|
176
|
+
pages << {:id => id}
|
177
|
+
else
|
178
|
+
pages << {:id => id, :site => site}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
haml :view, :locals => {:pages => pages}
|
182
|
+
end
|
183
|
+
|
184
|
+
get '/system/plugins.json' do
|
185
|
+
content_type 'application/json'
|
186
|
+
cross_origin
|
187
|
+
plugins = []
|
188
|
+
path = File.join(APP_ROOT, "client/plugins")
|
189
|
+
pathname = Pathname.new path
|
190
|
+
Dir.glob("#{path}/*/") {|filename| plugins << Pathname.new(filename).relative_path_from(pathname)}
|
191
|
+
JSON.pretty_generate plugins
|
192
|
+
end
|
193
|
+
|
194
|
+
get '/system/sitemap.json' do
|
195
|
+
content_type 'application/json'
|
196
|
+
cross_origin
|
197
|
+
pages = Store.annotated_pages farm_page.directory
|
198
|
+
sitemap = pages.collect {|p| {"slug" => p['name'], "title" => p['title'], "date" => p['updated_at'].to_i*1000, "synopsis" => synopsis(p)}}
|
199
|
+
JSON.pretty_generate sitemap
|
200
|
+
end
|
201
|
+
|
202
|
+
get '/system/factories.json' do
|
203
|
+
content_type 'application/json'
|
204
|
+
cross_origin
|
205
|
+
# return "[]"
|
206
|
+
factories = Dir.glob(File.join(APP_ROOT, "client/plugins/*/factory.json")).collect do |info|
|
207
|
+
begin
|
208
|
+
JSON.parse(File.read(info))
|
209
|
+
rescue
|
210
|
+
end
|
211
|
+
end.reject {|info| info.nil?}
|
212
|
+
JSON.pretty_generate factories
|
213
|
+
end
|
214
|
+
|
215
|
+
get %r{^/([a-z0-9-]+)\.json$} do |name|
|
216
|
+
content_type 'application/json'
|
217
|
+
serve_page name
|
218
|
+
end
|
219
|
+
|
220
|
+
error 403 do
|
221
|
+
'Access forbidden'
|
222
|
+
end
|
223
|
+
|
224
|
+
put %r{^/page/([a-z0-9-]+)/action$} do |name|
|
225
|
+
unless authenticated? or (!identified? and !claimed?)
|
226
|
+
halt 403
|
227
|
+
return
|
228
|
+
end
|
229
|
+
|
230
|
+
action = JSON.parse params['action']
|
231
|
+
if site = action['fork']
|
232
|
+
# this fork is bundled with some other action
|
233
|
+
page = JSON.parse RestClient.get("#{site}/#{name}.json")
|
234
|
+
( page['journal'] ||= [] ) << { 'type' => 'fork', 'site' => site }
|
235
|
+
farm_page.put name, page
|
236
|
+
action.delete 'fork'
|
237
|
+
elsif action['type'] == 'create'
|
238
|
+
return halt 409 if farm_page.exists?(name)
|
239
|
+
page = action['item'].clone
|
240
|
+
elsif action['type'] == 'fork'
|
241
|
+
if action['item']
|
242
|
+
page = action['item'].clone
|
243
|
+
action.delete 'item'
|
244
|
+
else
|
245
|
+
page = JSON.parse RestClient.get("#{action['site']}/#{name}.json")
|
246
|
+
end
|
247
|
+
else
|
248
|
+
page = farm_page.get(name)
|
249
|
+
end
|
250
|
+
|
251
|
+
case action['type']
|
252
|
+
when 'move'
|
253
|
+
page['story'] = action['order'].collect{ |id| page['story'].detect{ |item| item['id'] == id } || raise('Ignoring move. Try reload.') }
|
254
|
+
when 'add'
|
255
|
+
before = action['after'] ? 1+page['story'].index{|item| item['id'] == action['after']} : 0
|
256
|
+
page['story'].insert before, action['item']
|
257
|
+
when 'remove'
|
258
|
+
page['story'].delete_at page['story'].index{ |item| item['id'] == action['id'] }
|
259
|
+
when 'edit'
|
260
|
+
page['story'][page['story'].index{ |item| item['id'] == action['id'] }] = action['item']
|
261
|
+
when 'create', 'fork'
|
262
|
+
page['story'] ||= []
|
263
|
+
else
|
264
|
+
puts "unfamiliar action: #{action.inspect}"
|
265
|
+
status 501
|
266
|
+
return "unfamiliar action"
|
267
|
+
end
|
268
|
+
( page['journal'] ||= [] ) << action
|
269
|
+
farm_page.put name, page
|
270
|
+
"ok"
|
271
|
+
end
|
272
|
+
|
273
|
+
get %r{^/remote/([a-zA-Z0-9:\.-]+)/([a-z0-9-]+)\.json$} do |site, name|
|
274
|
+
content_type 'application/json'
|
275
|
+
host = site.split(':').first
|
276
|
+
if serve_resources_locally?(host)
|
277
|
+
serve_page(name, host)
|
278
|
+
else
|
279
|
+
RestClient.get "#{site}/#{name}.json" do |response, request, result, &block|
|
280
|
+
case response.code
|
281
|
+
when 200
|
282
|
+
response
|
283
|
+
when 404
|
284
|
+
halt 404
|
285
|
+
else
|
286
|
+
response.return!(request, result, &block)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
get %r{^/remote/([a-zA-Z0-9:\.-]+)/favicon.png$} do |site|
|
293
|
+
content_type 'image/png'
|
294
|
+
host = site.split(':').first
|
295
|
+
if serve_resources_locally?(host)
|
296
|
+
Favicon.get_or_create(File.join farm_status(host), 'favicon.png')
|
297
|
+
else
|
298
|
+
RestClient.get "#{site}/favicon.png"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
not_found do
|
303
|
+
oops 404, "Page not found"
|
304
|
+
end
|
305
|
+
|
306
|
+
put '/submit' do
|
307
|
+
content_type 'application/json'
|
308
|
+
bundle = JSON.parse params['bundle']
|
309
|
+
spawn = "#{(rand*1000000).to_i}.#{request.host}"
|
310
|
+
site = request.port == 80 ? spawn : "#{spawn}:#{request.port}"
|
311
|
+
bundle.each do |slug, page|
|
312
|
+
farm_page(spawn).put slug, page
|
313
|
+
end
|
314
|
+
citation = {
|
315
|
+
"type"=> "reference",
|
316
|
+
"id"=> RandomId.generate,
|
317
|
+
"site"=> site,
|
318
|
+
"slug"=> "recent-changes",
|
319
|
+
"title"=> "Recent Changes",
|
320
|
+
"text"=> bundle.collect{|slug, page| "<li> [[#{page['title']||slug}]]"}.join("\n")
|
321
|
+
}
|
322
|
+
action = {
|
323
|
+
"type"=> "add",
|
324
|
+
"id"=> citation['id'],
|
325
|
+
"date"=> Time.new.to_i*1000,
|
326
|
+
"item"=> citation
|
327
|
+
}
|
328
|
+
slug = 'recent-submissions'
|
329
|
+
page = farm_page.get slug
|
330
|
+
(page['story']||=[]) << citation
|
331
|
+
(page['journal']||=[]) << action
|
332
|
+
farm_page.put slug, page
|
333
|
+
JSON.pretty_generate citation
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|