yodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,21 @@
|
|
1
|
+
class Section < Array
|
2
|
+
attr_reader :name
|
3
|
+
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
def display?
|
10
|
+
any? {|field| display_field?(field)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def displayed_fields
|
14
|
+
select {|field| display_field?(field)}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def display_field?(field)
|
19
|
+
field.display? && field.default_input_type.present? && field.default_input_type != :embedded
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require './record/mongo_record'
|
2
|
+
require './model/site_model'
|
3
|
+
|
4
|
+
class SiteRecord < MongoRecord
|
5
|
+
extend SiteModel
|
6
|
+
attr_reader :site
|
7
|
+
|
8
|
+
def initialize(site, values={}, new_record=true)
|
9
|
+
@site = site
|
10
|
+
super(values, new_record)
|
11
|
+
end
|
12
|
+
|
13
|
+
def site_id; @values['_site_id']; end
|
14
|
+
|
15
|
+
def default_values
|
16
|
+
super.merge({'_site_id' => site.id})
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect_hash
|
20
|
+
{site_id: site_id}.merge(super)
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform_reload(params)
|
24
|
+
document = load_mongo_document(_id: params[:id])
|
25
|
+
initialize(params[:site], document)
|
26
|
+
end
|
27
|
+
|
28
|
+
def prepare_reload_params
|
29
|
+
super.tap {|vals| vals[:site] = site}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Migration
|
2
|
+
def self.run_migrations(site)
|
3
|
+
Yodel.config.logger.info "Migrating #{site.name}"
|
4
|
+
|
5
|
+
each_migration_for(site) do |migration, file|
|
6
|
+
unless migration.nil?
|
7
|
+
next if site.migrations.include?(migration.name)
|
8
|
+
migration.up(site)
|
9
|
+
|
10
|
+
# newly created models are incomplete; reload the site
|
11
|
+
# to force complete versions to be generated for use
|
12
|
+
site.migrations << migration.name
|
13
|
+
site.save
|
14
|
+
site.reload
|
15
|
+
|
16
|
+
Yodel.config.logger.info "Migrated #{migration.name}"
|
17
|
+
else
|
18
|
+
raise MissingMigration.new(file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Yodel.config.logger.info "Migrations for #{site.name} complete"
|
23
|
+
end
|
24
|
+
|
25
|
+
# As migration files are require'd this method will be triggered so
|
26
|
+
# we have a reference to the 'current' migration class being run
|
27
|
+
def self.inherited(child)
|
28
|
+
@migration = child
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
private
|
33
|
+
def self.each_migration_for(site, &block)
|
34
|
+
each_migration(File.join(site.migrations_directory, Yodel::YODEL_MIGRATIONS_DIRECTORY_NAME), &block)
|
35
|
+
each_migration(File.join(site.migrations_directory, Yodel::EXTENSION_MIGRATIONS_DIRECTORY_NAME), &block)
|
36
|
+
each_migration(File.join(site.migrations_directory, Yodel::SITE_MIGRATIONS_DIRECTORY_NAME), &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Iterate over every migration and yield the migration class
|
40
|
+
# to the supplied block. Incorrect migration files may result
|
41
|
+
# in nil being yielded. The caller can respond appropriately.
|
42
|
+
# The current file (a string path) is also provided.
|
43
|
+
def self.each_migration(directory)
|
44
|
+
return unless File.directory?(directory)
|
45
|
+
Dir[File.join(directory, '**/*.rb')].sort.each do |file|
|
46
|
+
@migration = nil
|
47
|
+
load file
|
48
|
+
yield @migration, file
|
49
|
+
Object.send(:remove_const, @migration.name.to_sym) if @migration
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Remote < MongoRecord
|
2
|
+
collection :remotes
|
3
|
+
field :name, :string, validations: {required: {}}
|
4
|
+
field :url, :string, validations: {required: {}}
|
5
|
+
field :username, :string, validations: {required: {}}
|
6
|
+
field :password, :password, validations: {required: {}}
|
7
|
+
many :sites, store: false
|
8
|
+
|
9
|
+
def site_list
|
10
|
+
perform_request('/sites.json', :get)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_site
|
14
|
+
perform_request('/sites.json', :post)
|
15
|
+
end
|
16
|
+
|
17
|
+
before_save :hash_password
|
18
|
+
def hash_password
|
19
|
+
return unless password_changed? && password?
|
20
|
+
self.password = Password.hashed_password(nil, password)
|
21
|
+
end
|
22
|
+
|
23
|
+
def host
|
24
|
+
URI.parse(url).host
|
25
|
+
end
|
26
|
+
|
27
|
+
def git_url(remote_id)
|
28
|
+
git_url = URI.parse(url).merge("/git/#{remote_id}")
|
29
|
+
git_url.user = CGI.escape(username)
|
30
|
+
git_url.password = password
|
31
|
+
git_url.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def perform_request(request_path, method)
|
36
|
+
case method
|
37
|
+
when :get
|
38
|
+
klass = Net::HTTP::Get
|
39
|
+
when :post
|
40
|
+
klass = Net::HTTP::Post
|
41
|
+
else
|
42
|
+
raise "Unknown remote request type"
|
43
|
+
end
|
44
|
+
uri = URI.parse(url)
|
45
|
+
|
46
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
47
|
+
request = klass.new(uri.merge(request_path).path, {'Content-Type' => 'application/json'})
|
48
|
+
request.basic_auth username, password
|
49
|
+
response = http.request(request, '')
|
50
|
+
if response.is_a?(Net::HTTPNotFound)
|
51
|
+
{'success' => false, 'reason' => 'Path or domain not found'}
|
52
|
+
elsif response.code == 302
|
53
|
+
{'success' => false, 'reason'=> 'Redirection not supported'}
|
54
|
+
else
|
55
|
+
JSON.parse(response.body)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ECONNRESET
|
59
|
+
{'success' => false, 'reason' => 'Remote host could not be contacted'}
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
class Site < MongoRecord
|
2
|
+
attr_reader :cached_records, :cached_models
|
3
|
+
|
4
|
+
collection :sites
|
5
|
+
field :name, :string
|
6
|
+
field :created_at, :time
|
7
|
+
field :root_directory, :string
|
8
|
+
field :remote_id, :string
|
9
|
+
field :model_plural_names, :hash
|
10
|
+
field :model_types, :hash
|
11
|
+
field :extensions, :array
|
12
|
+
field :migrations, :array
|
13
|
+
field :options, :hash
|
14
|
+
field :domains, :array
|
15
|
+
one :remote
|
16
|
+
|
17
|
+
def initialize(values={}, new_record=true)
|
18
|
+
super
|
19
|
+
@cached_records = {}
|
20
|
+
@cached_models = {}
|
21
|
+
|
22
|
+
# static models
|
23
|
+
@models = Model.scoped_for(self)
|
24
|
+
@cached_models['Model'] = @cached_models['models'] = @models
|
25
|
+
@cached_models['Trigger'] = @cached_models['triggers'] = Trigger.scoped_for(self)
|
26
|
+
@cached_models['Task'] = @cached_models['tasks'] = Task.scoped_for(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# ----------------------------------------
|
31
|
+
# Accessors
|
32
|
+
# ----------------------------------------
|
33
|
+
# TODO: a better interface is site.options.name.option; site.options.pages.permalink_character
|
34
|
+
def option(path)
|
35
|
+
component, option = path.split('.')
|
36
|
+
options[component].try(:fetch, option, nil).try(:fetch, 'value', nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
def log
|
40
|
+
@log ||= Log.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def public_directory
|
44
|
+
@public_dir ||= File.join(root_directory, Yodel::PUBLIC_DIRECTORY_NAME)
|
45
|
+
end
|
46
|
+
|
47
|
+
def public_directories
|
48
|
+
@public_dirs ||= Yodel.config.public_directories + [public_directory]
|
49
|
+
end
|
50
|
+
|
51
|
+
def layouts_directory
|
52
|
+
@layouts_dir ||= File.join(root_directory, Yodel::LAYOUTS_DIRECTORY_NAME)
|
53
|
+
end
|
54
|
+
|
55
|
+
def layout_directories
|
56
|
+
@layout_dirs ||= Yodel.config.layout_directories + [layouts_directory]
|
57
|
+
end
|
58
|
+
|
59
|
+
def partials_directory
|
60
|
+
@partials_dir ||= File.join(root_directory, Yodel::PARTIALS_DIRECTORY_NAME)
|
61
|
+
end
|
62
|
+
|
63
|
+
def migrations_directory
|
64
|
+
@migrations_dir ||= File.join(root_directory, Yodel::MIGRATIONS_DIRECTORY_NAME)
|
65
|
+
end
|
66
|
+
|
67
|
+
def attachments_directory
|
68
|
+
@attachments_dir ||= File.join(root_directory, Yodel::ATTACHMENTS_DIRECTORY_NAME)
|
69
|
+
end
|
70
|
+
|
71
|
+
def site_yaml_path
|
72
|
+
File.join(root_directory, Yodel::SITE_YML_FILE_NAME)
|
73
|
+
end
|
74
|
+
|
75
|
+
def local_domain
|
76
|
+
domains.find {|domain| domain.end_with?('.yodel')}
|
77
|
+
end
|
78
|
+
|
79
|
+
def remote_domains
|
80
|
+
domains.select do |domain|
|
81
|
+
!domain.end_with?('.yodel') &&
|
82
|
+
!domain.end_with?('.local') &&
|
83
|
+
!domain.end_with?('.localhost') &&
|
84
|
+
!domain.start_with?('192.168.') &&
|
85
|
+
!domain.start_with?('10.') &&
|
86
|
+
!domain.start_with?('127.') &&
|
87
|
+
(!domain.start_with?('172.') || !(16..31).include?(domain.split('.')[1].to_i)) &&
|
88
|
+
domain != '0.0.0.0' &&
|
89
|
+
domain != '255.255.255.255' &&
|
90
|
+
domain != 'localhost' &&
|
91
|
+
domain != 'broadcasthost'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def latest_revision
|
96
|
+
Dir.chdir(root_directory) do
|
97
|
+
`git log -n1 --pretty=format:"%H"`
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def latest_revision_date
|
102
|
+
Dir.chdir(root_directory) do
|
103
|
+
`git log -n1 --pretty=format:"%ai"`
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# ----------------------------------------
|
109
|
+
# Life cycle
|
110
|
+
# ----------------------------------------
|
111
|
+
before_destroy :destroy_records
|
112
|
+
def destroy_records
|
113
|
+
Trigger.collection.remove(_site_id: id)
|
114
|
+
LogEntry.collection.remove(_site_id: id)
|
115
|
+
Record.collection.remove(_site_id: id)
|
116
|
+
Model.collection.remove(_site_id: id)
|
117
|
+
Task.collection.remove(_site_id: id)
|
118
|
+
end
|
119
|
+
|
120
|
+
after_destroy :destroy_directories
|
121
|
+
def destroy_directories
|
122
|
+
# root directory
|
123
|
+
FileUtils.remove_entry_secure(root_directory) if File.directory?(root_directory)
|
124
|
+
|
125
|
+
# domain symlinks in production
|
126
|
+
if Yodel.env.production?
|
127
|
+
domains.each do |domain|
|
128
|
+
path = File.join(Yodel.config.public_directory, domain)
|
129
|
+
FileUtils.remove_file(path, true) if File.symlink?(path)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
after_save :update_site_yml
|
135
|
+
def update_site_yml
|
136
|
+
return unless Yodel.env.development?
|
137
|
+
File.open(site_yaml_path, 'w') do |file|
|
138
|
+
file.write(YAML.dump({
|
139
|
+
name: name.to_s,
|
140
|
+
extensions: extensions.to_a,
|
141
|
+
domains: remote_domains.to_a,
|
142
|
+
options: options.to_hash
|
143
|
+
}))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def reload_from_site_yaml
|
148
|
+
update(YAML.load_file(site_yaml_path))
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.load_from_site_yaml(path)
|
152
|
+
Site.new(YAML.load_file(path)).tap do |site|
|
153
|
+
site.root_directory = File.dirname(path)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# ----------------------------------------
|
159
|
+
# Model Lookups
|
160
|
+
# ----------------------------------------
|
161
|
+
# Method missing is utilised to allows lookups of models by their plural name
|
162
|
+
# directly on a site object. site.models is equivalent to site.model('Model')
|
163
|
+
def method_missing(name, *args, &block)
|
164
|
+
# attempt to find the model in the cached_models hash
|
165
|
+
key = name.to_s
|
166
|
+
model = @cached_models[key]
|
167
|
+
return model unless model.nil?
|
168
|
+
|
169
|
+
# otherwise perform a lookup
|
170
|
+
model = model_by_plural_name(key)
|
171
|
+
return super if model.nil?
|
172
|
+
model
|
173
|
+
end
|
174
|
+
|
175
|
+
# Retrieve a model by its plural name ('models' as opposed to 'Model'). In general
|
176
|
+
# use the method missing functionality of site since it checks the cached_models
|
177
|
+
# hash before performing a lookup, whereas this method will always do a lookup.
|
178
|
+
def model_by_plural_name(name)
|
179
|
+
# ensure the site has a reference to a model by this name. get is required here
|
180
|
+
# instead calling 'model_types' explicitly as that relies on method_missing
|
181
|
+
# which in turn sometimes calls this method (creating infinite recursion)
|
182
|
+
model_id = get('model_types')[name]
|
183
|
+
return nil if model_id.nil?
|
184
|
+
|
185
|
+
# perform a lookup; nil will be returned if the model doesn't exist
|
186
|
+
model = @models.find(model_id)
|
187
|
+
@cached_models[name] = model
|
188
|
+
@cached_models[model.name] = model
|
189
|
+
@cached_records[model.id] = model
|
190
|
+
end
|
191
|
+
|
192
|
+
# Retrieve a model by its full name ('Model' as opposed to 'models')
|
193
|
+
def model(name)
|
194
|
+
# get is required here instead calling 'model_types' explicitly as that relies
|
195
|
+
# on method_missing which in turn sometimes calls this method (creating
|
196
|
+
# infinite recursion)
|
197
|
+
return nil if name.nil?
|
198
|
+
model = @cached_models[name]
|
199
|
+
return model unless model.nil?
|
200
|
+
model_by_plural_name(get('model_plural_names')[name])
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class EmailAddressValidation < Validation
|
2
|
+
def self.validate(params, field, name, value, record, errors)
|
3
|
+
begin
|
4
|
+
# modified: http://my.rails-royce.org/2010/07/21/email-validation-in-ruby-on-rails-without-regexp/
|
5
|
+
address = Mail::Address.new(value)
|
6
|
+
|
7
|
+
# ensure there is a domain and the full parsed address is equivalent to the value
|
8
|
+
if address.domain.present? && address.address == value
|
9
|
+
# ensure the domain component is made up of more than just a TLD
|
10
|
+
domain_tree = address.send(:tree).domain
|
11
|
+
if domain_tree.dot_atom_text.elements.size > 1
|
12
|
+
return true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
|
18
|
+
errors[field.name] << new(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def describe
|
22
|
+
"must be a valid email address"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class EmbeddedRecordsValidation < Validation
|
2
|
+
def initialize(params, errors)
|
3
|
+
super(params)
|
4
|
+
@errors = errors
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.validate(field, records, record, errors)
|
8
|
+
# embedded record validations
|
9
|
+
records = [records] unless records.respond_to?(:to_a)
|
10
|
+
embedded_errors = Errors.new
|
11
|
+
records.to_a.each_with_index do |embedded_record, index|
|
12
|
+
embedded_errors[index] = embedded_record.valid? ? nil : embedded_record.errors
|
13
|
+
end
|
14
|
+
|
15
|
+
# field set validations
|
16
|
+
field.fields.each do |name, embedded_field|
|
17
|
+
next unless embedded_field.set_validations
|
18
|
+
set_value = records.to_a.collect {|embedded| embedded.get(name)}.uniq
|
19
|
+
embedded_field.set_validations.each do |type, params|
|
20
|
+
Validation.validate(type, params, embedded_field, name, set_value, record, embedded_errors)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
errors[field.name] = embedded_errors unless embedded_errors.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def describe
|
28
|
+
# FIXME: don't just call inspect here, format correctly using describe calls
|
29
|
+
"has these errors: #{@errors.inspect}}"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Errors
|
2
|
+
def initialize
|
3
|
+
@errors = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def inspect
|
7
|
+
@errors.inspect
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
@errors[name] ||= []
|
12
|
+
@errors[name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(name, value)
|
16
|
+
return if value.nil?
|
17
|
+
@errors[name] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def key?(name)
|
21
|
+
@errors.key?(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty?
|
25
|
+
@errors.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear
|
29
|
+
@errors.clear
|
30
|
+
@summary = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# errors on a collection
|
34
|
+
def <<(error)
|
35
|
+
self['_'] << error
|
36
|
+
end
|
37
|
+
|
38
|
+
def summarise
|
39
|
+
@summary ||= @errors.each_with_object({}) do |(field, errors), hash|
|
40
|
+
if errors.respond_to?(:summarise)
|
41
|
+
hash[field.to_s] = errors.summarise.values.to_sentence
|
42
|
+
else
|
43
|
+
hash[field.to_s] = "#{field.to_s.humanize} #{errors.collect(&:describe).to_sentence}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_json(*a)
|
49
|
+
summarise.to_json(*a)
|
50
|
+
end
|
51
|
+
end
|