wiki 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|