yodel 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.
- data/.document +5 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +1 -0
- data/README.rdoc +20 -0
- data/Rakefile +1 -0
- data/bin/yodel +4 -0
- data/lib/yodel.rb +22 -0
- data/lib/yodel/application/application.rb +44 -0
- data/lib/yodel/application/extension.rb +59 -0
- data/lib/yodel/application/request_handler.rb +48 -0
- data/lib/yodel/application/yodel.rb +25 -0
- data/lib/yodel/command/command.rb +94 -0
- data/lib/yodel/command/deploy.rb +67 -0
- data/lib/yodel/command/dns_server.rb +16 -0
- data/lib/yodel/command/installer.rb +229 -0
- data/lib/yodel/config/config.rb +30 -0
- data/lib/yodel/config/environment.rb +16 -0
- data/lib/yodel/config/yodel.rb +21 -0
- data/lib/yodel/exceptions/destroyed_record.rb +2 -0
- data/lib/yodel/exceptions/domain_not_found.rb +16 -0
- data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
- data/lib/yodel/exceptions/exceptions.rb +3 -0
- data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
- data/lib/yodel/exceptions/invalid_field.rb +2 -0
- data/lib/yodel/exceptions/invalid_index.rb +2 -0
- data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
- data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
- data/lib/yodel/exceptions/layout_not_found.rb +2 -0
- data/lib/yodel/exceptions/mass_assignment.rb +2 -0
- data/lib/yodel/exceptions/missing_migration.rb +2 -0
- data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
- data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
- data/lib/yodel/exceptions/unauthorised.rb +2 -0
- data/lib/yodel/exceptions/unknown_field.rb +2 -0
- data/lib/yodel/middleware/development_server.rb +180 -0
- data/lib/yodel/middleware/error_pages.rb +72 -0
- data/lib/yodel/middleware/public_assets.rb +78 -0
- data/lib/yodel/middleware/request.rb +16 -0
- data/lib/yodel/middleware/site_detector.rb +22 -0
- data/lib/yodel/mime_types/default_mime_set.rb +28 -0
- data/lib/yodel/mime_types/mime_type.rb +68 -0
- data/lib/yodel/mime_types/mime_type_set.rb +41 -0
- data/lib/yodel/mime_types/mime_types.rb +6 -0
- data/lib/yodel/mime_types/yodel.rb +15 -0
- data/lib/yodel/models/api/api.rb +1 -0
- data/lib/yodel/models/api/api_call.rb +87 -0
- data/lib/yodel/models/core/associations/association.rb +37 -0
- data/lib/yodel/models/core/associations/associations.rb +22 -0
- data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
- data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
- data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
- data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
- data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
- data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
- data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
- data/lib/yodel/models/core/associations/record_association.rb +38 -0
- data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
- data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
- data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
- data/lib/yodel/models/core/attachments/attachment.rb +73 -0
- data/lib/yodel/models/core/attachments/image.rb +38 -0
- data/lib/yodel/models/core/core.rb +15 -0
- data/lib/yodel/models/core/fields/alias_field.rb +32 -0
- data/lib/yodel/models/core/fields/array_field.rb +64 -0
- data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
- data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
- data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
- data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
- data/lib/yodel/models/core/fields/color_field.rb +4 -0
- data/lib/yodel/models/core/fields/date_field.rb +35 -0
- data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
- data/lib/yodel/models/core/fields/email_field.rb +10 -0
- data/lib/yodel/models/core/fields/enum_field.rb +33 -0
- data/lib/yodel/models/core/fields/field.rb +154 -0
- data/lib/yodel/models/core/fields/fields.rb +29 -0
- data/lib/yodel/models/core/fields/fields_field.rb +31 -0
- data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
- data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
- data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
- data/lib/yodel/models/core/fields/function_field.rb +28 -0
- data/lib/yodel/models/core/fields/hash_field.rb +54 -0
- data/lib/yodel/models/core/fields/html_field.rb +15 -0
- data/lib/yodel/models/core/fields/image_field.rb +11 -0
- data/lib/yodel/models/core/fields/integer_field.rb +25 -0
- data/lib/yodel/models/core/fields/password_field.rb +21 -0
- data/lib/yodel/models/core/fields/self_field.rb +27 -0
- data/lib/yodel/models/core/fields/string_field.rb +15 -0
- data/lib/yodel/models/core/fields/tags_field.rb +7 -0
- data/lib/yodel/models/core/fields/text_field.rb +7 -0
- data/lib/yodel/models/core/fields/time_field.rb +36 -0
- data/lib/yodel/models/core/functions/function.rb +471 -0
- data/lib/yodel/models/core/functions/functions.rb +2 -0
- data/lib/yodel/models/core/functions/trigger.rb +14 -0
- data/lib/yodel/models/core/log/log.rb +33 -0
- data/lib/yodel/models/core/log/log_entry.rb +12 -0
- data/lib/yodel/models/core/model/abstract_model.rb +59 -0
- data/lib/yodel/models/core/model/model.rb +460 -0
- data/lib/yodel/models/core/model/mongo_model.rb +25 -0
- data/lib/yodel/models/core/model/site_model.rb +17 -0
- data/lib/yodel/models/core/mongo/mongo.rb +3 -0
- data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
- data/lib/yodel/models/core/mongo/query.rb +68 -0
- data/lib/yodel/models/core/mongo/record_index.rb +89 -0
- data/lib/yodel/models/core/record/abstract_record.rb +411 -0
- data/lib/yodel/models/core/record/embedded_record.rb +47 -0
- data/lib/yodel/models/core/record/mongo_record.rb +83 -0
- data/lib/yodel/models/core/record/record.rb +386 -0
- data/lib/yodel/models/core/record/section.rb +21 -0
- data/lib/yodel/models/core/record/site_record.rb +31 -0
- data/lib/yodel/models/core/site/migration.rb +52 -0
- data/lib/yodel/models/core/site/remote.rb +61 -0
- data/lib/yodel/models/core/site/site.rb +202 -0
- data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
- data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
- data/lib/yodel/models/core/validations/errors.rb +51 -0
- data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
- data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
- data/lib/yodel/models/core/validations/format_validation.rb +10 -0
- data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
- data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
- data/lib/yodel/models/core/validations/length_validation.rb +28 -0
- data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
- data/lib/yodel/models/core/validations/required_validation.rb +9 -0
- data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
- data/lib/yodel/models/core/validations/validation.rb +39 -0
- data/lib/yodel/models/core/validations/validations.rb +15 -0
- data/lib/yodel/models/email/email.rb +79 -0
- data/lib/yodel/models/migrations/01_record_model.rb +29 -0
- data/lib/yodel/models/migrations/02_page_model.rb +45 -0
- data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
- data/lib/yodel/models/migrations/04_group_model.rb +61 -0
- data/lib/yodel/models/migrations/05_user_model.rb +24 -0
- data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
- data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
- data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
- data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
- data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
- data/lib/yodel/models/migrations/11_email_model.rb +28 -0
- data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
- data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
- data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
- data/lib/yodel/models/models.rb +8 -0
- data/lib/yodel/models/pages/form_builder.rb +379 -0
- data/lib/yodel/models/pages/html_decorator.rb +132 -0
- data/lib/yodel/models/pages/layout.rb +120 -0
- data/lib/yodel/models/pages/menu.rb +32 -0
- data/lib/yodel/models/pages/page.rb +378 -0
- data/lib/yodel/models/pages/pages.rb +7 -0
- data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
- data/lib/yodel/models/pages/redirect_page.rb +11 -0
- data/lib/yodel/models/search/search.rb +1 -0
- data/lib/yodel/models/search/search_page.rb +58 -0
- data/lib/yodel/models/security/facebook_login_page.rb +55 -0
- data/lib/yodel/models/security/group.rb +10 -0
- data/lib/yodel/models/security/guests_group.rb +5 -0
- data/lib/yodel/models/security/login_page.rb +20 -0
- data/lib/yodel/models/security/logout_page.rb +13 -0
- data/lib/yodel/models/security/noone_group.rb +5 -0
- data/lib/yodel/models/security/owner_group.rb +8 -0
- data/lib/yodel/models/security/password.rb +5 -0
- data/lib/yodel/models/security/password_reset_page.rb +47 -0
- data/lib/yodel/models/security/security.rb +10 -0
- data/lib/yodel/models/security/user.rb +33 -0
- data/lib/yodel/public/core/css/core.css +257 -0
- data/lib/yodel/public/core/css/reset.css +48 -0
- data/lib/yodel/public/core/images/cross.png +0 -0
- data/lib/yodel/public/core/images/spinner.gif +0 -0
- data/lib/yodel/public/core/images/tick.png +0 -0
- data/lib/yodel/public/core/images/yodel.png +0 -0
- data/lib/yodel/public/core/js/jquery.min.js +18 -0
- data/lib/yodel/public/core/js/json2.js +480 -0
- data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
- data/lib/yodel/request/authentication.rb +76 -0
- data/lib/yodel/request/flash.rb +28 -0
- data/lib/yodel/request/request.rb +4 -0
- data/lib/yodel/requires.rb +47 -0
- data/lib/yodel/task_queue/queue_daemon.rb +33 -0
- data/lib/yodel/task_queue/queue_worker.rb +32 -0
- data/lib/yodel/task_queue/stats_thread.rb +27 -0
- data/lib/yodel/task_queue/task.rb +62 -0
- data/lib/yodel/task_queue/task_queue.rb +40 -0
- data/lib/yodel/types/date.rb +5 -0
- data/lib/yodel/types/object_id.rb +11 -0
- data/lib/yodel/types/time.rb +5 -0
- data/lib/yodel/version.rb +3 -0
- data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
- data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
- data/system/etc/resolver/yodel +2 -0
- data/system/usr/local/bin/yodel_command_runner +2 -0
- data/system/usr/local/etc/yodel/development_settings.rb +28 -0
- data/system/usr/local/etc/yodel/production_settings.rb +27 -0
- data/system/var/log/yodel.log +0 -0
- data/test/helper.rb +18 -0
- data/test/test_yodel.rb +4 -0
- data/yodel.gemspec +47 -0
- metadata +501 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class ErrorPages
|
|
2
|
+
def initialize(app)
|
|
3
|
+
@app = app
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def call(env)
|
|
7
|
+
status, headers, response = @app.call(env)
|
|
8
|
+
if status.to_i >= 400 && status.to_i != 401
|
|
9
|
+
return render_error_page(status, response)
|
|
10
|
+
else
|
|
11
|
+
return [status, headers, response]
|
|
12
|
+
end
|
|
13
|
+
rescue
|
|
14
|
+
Yodel.config.logger.warn $!.to_s
|
|
15
|
+
if Yodel.env.production?
|
|
16
|
+
if $!.is_a?(DomainNotFound)
|
|
17
|
+
return render_error_page(404, ['No site has been set up at this address'])
|
|
18
|
+
else
|
|
19
|
+
return render_error_page(500, [])
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
if $!.respond_to?(:error) && $!.respond_to?(:description)
|
|
23
|
+
return render_error_page(404, $!.error, $!.description)
|
|
24
|
+
else
|
|
25
|
+
raise $!
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def render_error_page(error_code, response, description=nil)
|
|
31
|
+
template = Ember::Template.new(TEMPLATE)
|
|
32
|
+
if response.present? && response.respond_to?(:length) && response.length > 0
|
|
33
|
+
components = []
|
|
34
|
+
response.each {|component| components << component}
|
|
35
|
+
error = components.join
|
|
36
|
+
elsif error_code == 403
|
|
37
|
+
error = "Unauthorised"
|
|
38
|
+
else
|
|
39
|
+
error = "We're sorry, something went wrong."
|
|
40
|
+
end
|
|
41
|
+
if description.nil?
|
|
42
|
+
if error_code == 404
|
|
43
|
+
description = "You may have mistyped the address or the page may have moved."
|
|
44
|
+
elsif error_code == 403
|
|
45
|
+
description = "You must log in before performing this action."
|
|
46
|
+
else
|
|
47
|
+
description = "We've been notified about this issue and we'll take a look at it shortly."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
[error_code, {'Content-Type' => 'text/html'}, [template.render(binding)]]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
TEMPLATE = <<HTML
|
|
54
|
+
<!DOCTYPE html>
|
|
55
|
+
<html>
|
|
56
|
+
<head>
|
|
57
|
+
<title><%= error_code %> - <%= error %></title>
|
|
58
|
+
<link rel="stylesheet" href="/core/css/core.css" type="text/css">
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<article id="modal">
|
|
62
|
+
<header>
|
|
63
|
+
<h1>yodel</h1>
|
|
64
|
+
<div id="lip"></div>
|
|
65
|
+
</header>
|
|
66
|
+
<h1><%= error %></h1>
|
|
67
|
+
<p><%= description %></p>
|
|
68
|
+
</article>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
71
|
+
HTML
|
|
72
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
require 'rack/mime'
|
|
3
|
+
|
|
4
|
+
# some code copied from Rack::File
|
|
5
|
+
class PublicAssets
|
|
6
|
+
SEPARATORS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
parts = Rack::Utils.unescape(env["PATH_INFO"]).split(SEPARATORS)
|
|
14
|
+
return [403, {"Content-Type" => "text/plain"}, "Forbidden"] if parts.include? ".."
|
|
15
|
+
request = Rack::Request.new(env)
|
|
16
|
+
site = env['yodel.site']
|
|
17
|
+
|
|
18
|
+
unless site.nil?
|
|
19
|
+
public_directories = site.public_directories
|
|
20
|
+
else
|
|
21
|
+
public_directories = Yodel.config.public_directories
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
public_directories.each do |public_dir|
|
|
25
|
+
path = File.join(public_dir, *parts)
|
|
26
|
+
if File.file?(path) && File.readable?(path)
|
|
27
|
+
return dup.serve_file(path, env)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# raise any delayed exceptions. These are delayed till this middleware so
|
|
32
|
+
# the assets needed to render the error pages for them are available. Without
|
|
33
|
+
# this delay, a DomainNotFound exception raised in SiteDetector would prevent
|
|
34
|
+
# PublicAssest from serving the css or images used on the error page.
|
|
35
|
+
raise DomainNotFound.new(request.host, request.port) if site.nil?
|
|
36
|
+
raise MissingRootDirectory.new(site, request.port) if !File.directory?(site.root_directory)
|
|
37
|
+
@app.call(env)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def each
|
|
41
|
+
File.open(@path, 'rb') do |file|
|
|
42
|
+
file.seek(@range.begin)
|
|
43
|
+
remaining_len = @range.end - @range.begin + 1
|
|
44
|
+
while remaining_len > 0
|
|
45
|
+
part = file.read([8192, remaining_len].min)
|
|
46
|
+
break unless part
|
|
47
|
+
remaining_len -= part.length
|
|
48
|
+
yield part
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
def serve_file(path, env)
|
|
55
|
+
response = [200, {
|
|
56
|
+
"Last-Modified" => File.mtime(path).httpdate,
|
|
57
|
+
"Content-Type" => Rack::Mime.mime_type(File.extname(path), 'text/plain')
|
|
58
|
+
}, self]
|
|
59
|
+
|
|
60
|
+
size = FileTest.size?(path) || Rack::Utils.bytesize(IO.read(path))
|
|
61
|
+
ranges = Rack::Utils.byte_ranges(env, size)
|
|
62
|
+
|
|
63
|
+
if ranges.nil? || ranges.length > 1
|
|
64
|
+
@range = 0..size-1
|
|
65
|
+
elsif ranges.empty?
|
|
66
|
+
return [416, {"Content-Type" => "text/plain"}, "Byte range unsatisfiable"]
|
|
67
|
+
else # partial content
|
|
68
|
+
@range = ranges[0]
|
|
69
|
+
response[0] = 206
|
|
70
|
+
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
|
|
71
|
+
size = @range.end - @range.begin + 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
response[1]["Content-Length"] = size.to_s
|
|
75
|
+
@path = path
|
|
76
|
+
response
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
class Request
|
|
3
|
+
# almost verbatim copy of the 'url' method; just we
|
|
4
|
+
# don't append the full_path to the constructed url
|
|
5
|
+
def scheme_and_host
|
|
6
|
+
url = scheme + "://"
|
|
7
|
+
url << host
|
|
8
|
+
|
|
9
|
+
if scheme == "https" && port != 443 ||
|
|
10
|
+
scheme == "http" && port != 80
|
|
11
|
+
url << ":#{port}"
|
|
12
|
+
end
|
|
13
|
+
url
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class SiteDetector
|
|
2
|
+
def initialize(app)
|
|
3
|
+
@app = app
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def call(env)
|
|
7
|
+
request = Rack::Request.new(env)
|
|
8
|
+
site = Site.where(domains: request.host).first
|
|
9
|
+
env['yodel.site'] = site
|
|
10
|
+
|
|
11
|
+
unless site.nil?
|
|
12
|
+
if Yodel.env.production?
|
|
13
|
+
raise MissingRootDirectory.new(site, request.port) if !File.directory?(site.root_directory)
|
|
14
|
+
env['rack.session.options'][:domain] = ".#{site.domains.first}"
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
raise DomainNotFound.new(request.host, request.port) if Yodel.env.production?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@app.call(env)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Yodel.mime_types do
|
|
2
|
+
mime_type :html do
|
|
3
|
+
extensions 'html', 'htm', 'shtml'
|
|
4
|
+
mime_types 'text/html'
|
|
5
|
+
auth_type :page
|
|
6
|
+
layout_processor :ember
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
mime_type :json do
|
|
10
|
+
extensions 'json'
|
|
11
|
+
mime_types 'application/json'
|
|
12
|
+
layout_processor :eval
|
|
13
|
+
auth_type :basic
|
|
14
|
+
processor do |data|
|
|
15
|
+
JSON.generate(data)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
mime_type :atom do
|
|
20
|
+
extensions 'atom'
|
|
21
|
+
mime_types 'application/atom+xml'
|
|
22
|
+
layout_processor :eval
|
|
23
|
+
auth_type :basic
|
|
24
|
+
builder do
|
|
25
|
+
Builder::XmlMarkup.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
class MimeType
|
|
2
|
+
attr_accessor :name, :extensions, :mime_types
|
|
3
|
+
def initialize(name)
|
|
4
|
+
@name = name
|
|
5
|
+
@extensions = []
|
|
6
|
+
@mime_types = []
|
|
7
|
+
@processor = nil
|
|
8
|
+
@builder = nil
|
|
9
|
+
@auth_type = :page
|
|
10
|
+
@layout_processor = :ember
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def mime_types(*types)
|
|
14
|
+
@mime_types += types
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extensions(*exts)
|
|
18
|
+
@extensions += exts
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default_extension(ext=nil)
|
|
22
|
+
@extensions.first
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default_mime_type
|
|
26
|
+
@mime_types.first
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_builder?
|
|
30
|
+
!@builder.nil?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def builder(&block)
|
|
34
|
+
@builder = block
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_builder
|
|
38
|
+
@builder ? @builder.call : nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def has_processor?
|
|
42
|
+
!@processor.nil?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def processor(&block)
|
|
46
|
+
@processor = block
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process(data)
|
|
50
|
+
@processor ? @processor.call(data) : data
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def layout_processor(*processor)
|
|
54
|
+
if processor.empty?
|
|
55
|
+
@layout_processor
|
|
56
|
+
else
|
|
57
|
+
@layout_processor = processor.first
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def auth_type(*type)
|
|
62
|
+
if type.empty?
|
|
63
|
+
@auth_type
|
|
64
|
+
else
|
|
65
|
+
@auth_type = type.first
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class MimeTypeSet
|
|
2
|
+
attr_accessor :types
|
|
3
|
+
def initialize
|
|
4
|
+
@default = nil # default mime type when no other can be matched
|
|
5
|
+
@types = {} # index by type name (:html)
|
|
6
|
+
@extensions = {} # index by type extensions (html, htm)
|
|
7
|
+
@mime_types = {} # index by mime types (text/html)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def each
|
|
11
|
+
@types.values.each do |type|
|
|
12
|
+
yield type
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](name)
|
|
17
|
+
@types[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def <<(type)
|
|
21
|
+
@default ||= type
|
|
22
|
+
@types[type.name] = type
|
|
23
|
+
type.extensions.each {|extension| @extensions[extension] = type}
|
|
24
|
+
type.mime_types.each {|mime_type| @mime_types[mime_type] = type}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def mime_type_for_request(format, accept)
|
|
28
|
+
# try to match by file extension first
|
|
29
|
+
return @extensions[format] if format && @extensions.has_key?(format)
|
|
30
|
+
|
|
31
|
+
# parse the accept string and try to match by mime type. The accept
|
|
32
|
+
# header looks like: application/xml,text/html;q=0.9
|
|
33
|
+
accept.to_s.split(',').each do |mime_type|
|
|
34
|
+
name = mime_type.split(';').first
|
|
35
|
+
return @mime_types[name] if @mime_types.has_key?(name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# as a last resort, respond with the first mime type defined
|
|
39
|
+
@default
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Yodel
|
|
2
|
+
def self.mime_types(&block)
|
|
3
|
+
if block_given?
|
|
4
|
+
instance_eval &block
|
|
5
|
+
else
|
|
6
|
+
@mime_type_set ||= MimeTypeSet.new
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.mime_type(name, &block)
|
|
11
|
+
mime_type = MimeType.new(name)
|
|
12
|
+
mime_type.instance_eval &block
|
|
13
|
+
mime_types << mime_type
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require './api/api_call'
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class APICall < Record
|
|
2
|
+
def call(data)
|
|
3
|
+
Task.add_task(:call_api, encode_data_hash(data), site)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def perform_call(data)
|
|
7
|
+
data = decode_data_hash(data)
|
|
8
|
+
mime = Yodel.mime_types[mime_type.to_sym]
|
|
9
|
+
headers = {'Content-Type' => mime.default_mime_type}
|
|
10
|
+
|
|
11
|
+
# FIXME: reloading should be done elsewhere, not a concern of APICall
|
|
12
|
+
#Layout.reload_layouts(site) if Yodel.env.development?
|
|
13
|
+
if body_layout && layout = site.layouts.where(name: body_layout, mime_type: mime_type).first
|
|
14
|
+
@data = data
|
|
15
|
+
set_content(body)
|
|
16
|
+
set_binding(binding)
|
|
17
|
+
payload = mime.process(layout.render(self))
|
|
18
|
+
else
|
|
19
|
+
payload = body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Net::HTTP.start(domain, port) do |http|
|
|
23
|
+
request = request_class.new(path, headers)
|
|
24
|
+
case authentication
|
|
25
|
+
when 'basic'
|
|
26
|
+
request.basic_auth username, password
|
|
27
|
+
when 'digest'
|
|
28
|
+
# FIXME: implement
|
|
29
|
+
end
|
|
30
|
+
response = http.request(request, payload)
|
|
31
|
+
Function.new(function).execute(binding)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def content
|
|
37
|
+
@content
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_content(content)
|
|
41
|
+
@content = content
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_binding
|
|
45
|
+
@binding
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def set_binding(binding)
|
|
49
|
+
@binding = binding
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def data
|
|
53
|
+
@data
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
def encode_data_hash(data)
|
|
59
|
+
({_id: self.id}).tap do |encoded|
|
|
60
|
+
data.each do |key, value|
|
|
61
|
+
value = {_id: value.id} if value.is_a?(Record)
|
|
62
|
+
encoded[key] = value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def decode_data_hash(data)
|
|
68
|
+
data.each do |key, value|
|
|
69
|
+
if value.is_a?(Hash) && value.key?('_id')
|
|
70
|
+
data[key] = site.records.find(value['_id'])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def request_class
|
|
76
|
+
case self.http_method.downcase
|
|
77
|
+
when 'get'
|
|
78
|
+
Net::HTTP::Get
|
|
79
|
+
when 'post'
|
|
80
|
+
Net::HTTP::Post
|
|
81
|
+
when 'put'
|
|
82
|
+
Net::HTTP::Put
|
|
83
|
+
when 'delete'
|
|
84
|
+
Net::HTTP::Delete
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|