yodel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +63 -0
  5. data/LICENSE +1 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +1 -0
  8. data/bin/yodel +4 -0
  9. data/lib/yodel.rb +22 -0
  10. data/lib/yodel/application/application.rb +44 -0
  11. data/lib/yodel/application/extension.rb +59 -0
  12. data/lib/yodel/application/request_handler.rb +48 -0
  13. data/lib/yodel/application/yodel.rb +25 -0
  14. data/lib/yodel/command/command.rb +94 -0
  15. data/lib/yodel/command/deploy.rb +67 -0
  16. data/lib/yodel/command/dns_server.rb +16 -0
  17. data/lib/yodel/command/installer.rb +229 -0
  18. data/lib/yodel/config/config.rb +30 -0
  19. data/lib/yodel/config/environment.rb +16 -0
  20. data/lib/yodel/config/yodel.rb +21 -0
  21. data/lib/yodel/exceptions/destroyed_record.rb +2 -0
  22. data/lib/yodel/exceptions/domain_not_found.rb +16 -0
  23. data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
  24. data/lib/yodel/exceptions/exceptions.rb +3 -0
  25. data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
  26. data/lib/yodel/exceptions/invalid_field.rb +2 -0
  27. data/lib/yodel/exceptions/invalid_index.rb +2 -0
  28. data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
  29. data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
  30. data/lib/yodel/exceptions/layout_not_found.rb +2 -0
  31. data/lib/yodel/exceptions/mass_assignment.rb +2 -0
  32. data/lib/yodel/exceptions/missing_migration.rb +2 -0
  33. data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
  34. data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
  35. data/lib/yodel/exceptions/unauthorised.rb +2 -0
  36. data/lib/yodel/exceptions/unknown_field.rb +2 -0
  37. data/lib/yodel/middleware/development_server.rb +180 -0
  38. data/lib/yodel/middleware/error_pages.rb +72 -0
  39. data/lib/yodel/middleware/public_assets.rb +78 -0
  40. data/lib/yodel/middleware/request.rb +16 -0
  41. data/lib/yodel/middleware/site_detector.rb +22 -0
  42. data/lib/yodel/mime_types/default_mime_set.rb +28 -0
  43. data/lib/yodel/mime_types/mime_type.rb +68 -0
  44. data/lib/yodel/mime_types/mime_type_set.rb +41 -0
  45. data/lib/yodel/mime_types/mime_types.rb +6 -0
  46. data/lib/yodel/mime_types/yodel.rb +15 -0
  47. data/lib/yodel/models/api/api.rb +1 -0
  48. data/lib/yodel/models/api/api_call.rb +87 -0
  49. data/lib/yodel/models/core/associations/association.rb +37 -0
  50. data/lib/yodel/models/core/associations/associations.rb +22 -0
  51. data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
  52. data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
  53. data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
  54. data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
  55. data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
  56. data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
  57. data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
  58. data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
  59. data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
  60. data/lib/yodel/models/core/associations/record_association.rb +38 -0
  61. data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
  62. data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
  63. data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
  64. data/lib/yodel/models/core/attachments/attachment.rb +73 -0
  65. data/lib/yodel/models/core/attachments/image.rb +38 -0
  66. data/lib/yodel/models/core/core.rb +15 -0
  67. data/lib/yodel/models/core/fields/alias_field.rb +32 -0
  68. data/lib/yodel/models/core/fields/array_field.rb +64 -0
  69. data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
  70. data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
  71. data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
  72. data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
  73. data/lib/yodel/models/core/fields/color_field.rb +4 -0
  74. data/lib/yodel/models/core/fields/date_field.rb +35 -0
  75. data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
  76. data/lib/yodel/models/core/fields/email_field.rb +10 -0
  77. data/lib/yodel/models/core/fields/enum_field.rb +33 -0
  78. data/lib/yodel/models/core/fields/field.rb +154 -0
  79. data/lib/yodel/models/core/fields/fields.rb +29 -0
  80. data/lib/yodel/models/core/fields/fields_field.rb +31 -0
  81. data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
  82. data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
  83. data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
  84. data/lib/yodel/models/core/fields/function_field.rb +28 -0
  85. data/lib/yodel/models/core/fields/hash_field.rb +54 -0
  86. data/lib/yodel/models/core/fields/html_field.rb +15 -0
  87. data/lib/yodel/models/core/fields/image_field.rb +11 -0
  88. data/lib/yodel/models/core/fields/integer_field.rb +25 -0
  89. data/lib/yodel/models/core/fields/password_field.rb +21 -0
  90. data/lib/yodel/models/core/fields/self_field.rb +27 -0
  91. data/lib/yodel/models/core/fields/string_field.rb +15 -0
  92. data/lib/yodel/models/core/fields/tags_field.rb +7 -0
  93. data/lib/yodel/models/core/fields/text_field.rb +7 -0
  94. data/lib/yodel/models/core/fields/time_field.rb +36 -0
  95. data/lib/yodel/models/core/functions/function.rb +471 -0
  96. data/lib/yodel/models/core/functions/functions.rb +2 -0
  97. data/lib/yodel/models/core/functions/trigger.rb +14 -0
  98. data/lib/yodel/models/core/log/log.rb +33 -0
  99. data/lib/yodel/models/core/log/log_entry.rb +12 -0
  100. data/lib/yodel/models/core/model/abstract_model.rb +59 -0
  101. data/lib/yodel/models/core/model/model.rb +460 -0
  102. data/lib/yodel/models/core/model/mongo_model.rb +25 -0
  103. data/lib/yodel/models/core/model/site_model.rb +17 -0
  104. data/lib/yodel/models/core/mongo/mongo.rb +3 -0
  105. data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
  106. data/lib/yodel/models/core/mongo/query.rb +68 -0
  107. data/lib/yodel/models/core/mongo/record_index.rb +89 -0
  108. data/lib/yodel/models/core/record/abstract_record.rb +411 -0
  109. data/lib/yodel/models/core/record/embedded_record.rb +47 -0
  110. data/lib/yodel/models/core/record/mongo_record.rb +83 -0
  111. data/lib/yodel/models/core/record/record.rb +386 -0
  112. data/lib/yodel/models/core/record/section.rb +21 -0
  113. data/lib/yodel/models/core/record/site_record.rb +31 -0
  114. data/lib/yodel/models/core/site/migration.rb +52 -0
  115. data/lib/yodel/models/core/site/remote.rb +61 -0
  116. data/lib/yodel/models/core/site/site.rb +202 -0
  117. data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
  118. data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
  119. data/lib/yodel/models/core/validations/errors.rb +51 -0
  120. data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
  121. data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
  122. data/lib/yodel/models/core/validations/format_validation.rb +10 -0
  123. data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
  124. data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
  125. data/lib/yodel/models/core/validations/length_validation.rb +28 -0
  126. data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
  127. data/lib/yodel/models/core/validations/required_validation.rb +9 -0
  128. data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
  129. data/lib/yodel/models/core/validations/validation.rb +39 -0
  130. data/lib/yodel/models/core/validations/validations.rb +15 -0
  131. data/lib/yodel/models/email/email.rb +79 -0
  132. data/lib/yodel/models/migrations/01_record_model.rb +29 -0
  133. data/lib/yodel/models/migrations/02_page_model.rb +45 -0
  134. data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
  135. data/lib/yodel/models/migrations/04_group_model.rb +61 -0
  136. data/lib/yodel/models/migrations/05_user_model.rb +24 -0
  137. data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
  138. data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
  139. data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
  140. data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
  141. data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
  142. data/lib/yodel/models/migrations/11_email_model.rb +28 -0
  143. data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
  144. data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
  145. data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
  146. data/lib/yodel/models/models.rb +8 -0
  147. data/lib/yodel/models/pages/form_builder.rb +379 -0
  148. data/lib/yodel/models/pages/html_decorator.rb +132 -0
  149. data/lib/yodel/models/pages/layout.rb +120 -0
  150. data/lib/yodel/models/pages/menu.rb +32 -0
  151. data/lib/yodel/models/pages/page.rb +378 -0
  152. data/lib/yodel/models/pages/pages.rb +7 -0
  153. data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
  154. data/lib/yodel/models/pages/redirect_page.rb +11 -0
  155. data/lib/yodel/models/search/search.rb +1 -0
  156. data/lib/yodel/models/search/search_page.rb +58 -0
  157. data/lib/yodel/models/security/facebook_login_page.rb +55 -0
  158. data/lib/yodel/models/security/group.rb +10 -0
  159. data/lib/yodel/models/security/guests_group.rb +5 -0
  160. data/lib/yodel/models/security/login_page.rb +20 -0
  161. data/lib/yodel/models/security/logout_page.rb +13 -0
  162. data/lib/yodel/models/security/noone_group.rb +5 -0
  163. data/lib/yodel/models/security/owner_group.rb +8 -0
  164. data/lib/yodel/models/security/password.rb +5 -0
  165. data/lib/yodel/models/security/password_reset_page.rb +47 -0
  166. data/lib/yodel/models/security/security.rb +10 -0
  167. data/lib/yodel/models/security/user.rb +33 -0
  168. data/lib/yodel/public/core/css/core.css +257 -0
  169. data/lib/yodel/public/core/css/reset.css +48 -0
  170. data/lib/yodel/public/core/images/cross.png +0 -0
  171. data/lib/yodel/public/core/images/spinner.gif +0 -0
  172. data/lib/yodel/public/core/images/tick.png +0 -0
  173. data/lib/yodel/public/core/images/yodel.png +0 -0
  174. data/lib/yodel/public/core/js/jquery.min.js +18 -0
  175. data/lib/yodel/public/core/js/json2.js +480 -0
  176. data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
  177. data/lib/yodel/request/authentication.rb +76 -0
  178. data/lib/yodel/request/flash.rb +28 -0
  179. data/lib/yodel/request/request.rb +4 -0
  180. data/lib/yodel/requires.rb +47 -0
  181. data/lib/yodel/task_queue/queue_daemon.rb +33 -0
  182. data/lib/yodel/task_queue/queue_worker.rb +32 -0
  183. data/lib/yodel/task_queue/stats_thread.rb +27 -0
  184. data/lib/yodel/task_queue/task.rb +62 -0
  185. data/lib/yodel/task_queue/task_queue.rb +40 -0
  186. data/lib/yodel/types/date.rb +5 -0
  187. data/lib/yodel/types/object_id.rb +11 -0
  188. data/lib/yodel/types/time.rb +5 -0
  189. data/lib/yodel/version.rb +3 -0
  190. data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
  191. data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
  192. data/system/etc/resolver/yodel +2 -0
  193. data/system/usr/local/bin/yodel_command_runner +2 -0
  194. data/system/usr/local/etc/yodel/development_settings.rb +28 -0
  195. data/system/usr/local/etc/yodel/production_settings.rb +27 -0
  196. data/system/var/log/yodel.log +0 -0
  197. data/test/helper.rb +18 -0
  198. data/test/test_yodel.rb +4 -0
  199. data/yodel.gemspec +47 -0
  200. metadata +501 -0
@@ -0,0 +1,10 @@
1
+ class ExcludedFromValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ prohibited_values = params['prohibited_values']
4
+ errors[field.name] << new(prohibited_values) if prohibited_values.include?(value)
5
+ end
6
+
7
+ def describe
8
+ "must not be: #{params.join(', ')}"
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ class ExcludesCombinationsValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ combinations = params['combinations']
4
+ combinations.each do |excluded_combination|
5
+ fail = excluded_combination.all? {|prohibited| value.include?(prohibited)}
6
+ (errors[field.name] << new(combinations)) and return if fail
7
+ end
8
+ end
9
+
10
+ def describe
11
+ if params.size > 1
12
+ combinations = params.collect.with_index {|combo, index| "#{index + 1}. #{combo.to_sentence}"}
13
+ else
14
+ combinations = [params.first.to_sentence]
15
+ end
16
+ "may not contain #{combinations.size == 1 ? 'this' : 'these'} combination#{'s' if combinations.size > 1}: #{combinations.to_sentence(two_words_connector: ', and ')}"
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ class FormatValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ format = params['format']
4
+ errors[field.name] << new(format) unless value =~ Regexp.new(format)
5
+ end
6
+
7
+ def describe
8
+ "is not in the required format"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class IncludedInValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ valid_values = params['valid_values']
4
+ errors[field.name] << new(valid_values) unless valid_values.include?(value)
5
+ end
6
+
7
+ def describe
8
+ "must be one of: #{params.join(', ')}"
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ class IncludesCombinationsValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ combinations = params['combinations']
4
+ combinations.each do |included_combination|
5
+ return if included_combination.all? {|required| value.include?(required)}
6
+ end
7
+ errors[field.name] << new(combinations)
8
+ end
9
+
10
+ def describe
11
+ combinations = params.collect(&:to_s).join(', ')
12
+ "must contain one of these combinations: #{combinations}"
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ class LengthValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ length = params['length']
4
+ min, max = length
5
+
6
+ if min == 0
7
+ valid = (value.size <= max)
8
+ elsif max == 0
9
+ valid = (value.size >= min)
10
+ else
11
+ valid = (value.size >= min) && (value.size <= max)
12
+ end
13
+
14
+ errors[field.name] << new(length) unless valid
15
+ end
16
+
17
+ def describe
18
+ min, max = params
19
+
20
+ if min == 0
21
+ "is too long (maximum length is #{max})"
22
+ elsif max == 0
23
+ "is too short (minimum length is #{min})"
24
+ else
25
+ "must be between #{min} and #{max}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ class PasswordConfirmationValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ return if record.field_was(field.name).nil? || record.stash["current_#{name}"].nil? || !record.respond_to?(:passwords_match?)
4
+ match = record.passwords_match?(record.stash["current_#{name}"])
5
+ errors[field.name] << new(name) unless match
6
+ end
7
+
8
+ def describe
9
+ "didn't match your existing #{params}"
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class RequiredValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ errors[field.name] << new(params) if (value.blank? && !value.is_a?(FalseClass))
4
+ end
5
+
6
+ def describe
7
+ "is required"
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class UniqueValidation < Validation
2
+ def self.validate(params, field, name, value, record, errors)
3
+ errors[field.name] << new(params) if record.model.exists?(field.name => value, :_id.ne => record.id)
4
+ end
5
+
6
+ def describe
7
+ "must be unique"
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ class Validation
2
+ attr_accessor :params, :field
3
+
4
+ def self.validate(type, params, field, name, value, record, errors)
5
+ validation = case type
6
+ when 'excluded_from'
7
+ ExcludedFromValidation
8
+ when 'excludes_combinations'
9
+ ExcludesCombinationsValidation
10
+ when 'format'
11
+ FormatValidation
12
+ when 'included_in'
13
+ IncludedInValidation
14
+ when 'includes_combinations'
15
+ IncludesCombinationsValidation
16
+ when 'length'
17
+ LengthValidation
18
+ when 'required'
19
+ RequiredValidation
20
+ when 'unique'
21
+ UniqueValidation
22
+ when 'password_confirmation'
23
+ PasswordConfirmationValidation
24
+ end
25
+ validation.validate(params, field, name, value, record, errors)
26
+ end
27
+
28
+ def initialize(params)
29
+ @params = params
30
+ end
31
+
32
+ def describe
33
+ "is invalid"
34
+ end
35
+
36
+ def to_json(*a)
37
+ describe.to_json(*a)
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ Dir.chdir(File.dirname(__FILE__)) do
2
+ require './validation'
3
+ require './email_address_validation'
4
+ require './embedded_records_validation'
5
+ require './errors'
6
+ require './excluded_from_validation'
7
+ require './excludes_combinations_validation'
8
+ require './format_validation'
9
+ require './included_in_validation'
10
+ require './includes_combinations_validation'
11
+ require './length_validation'
12
+ require './password_confirmation_validation'
13
+ require './required_validation'
14
+ require './unique_validation'
15
+ end
@@ -0,0 +1,79 @@
1
+ class Email < Record
2
+
3
+ def deliver(options)
4
+ # we don't store complete records in the queue task, only IDs of records
5
+ cleaned = {_id: self.id}
6
+ options.each do |key, value|
7
+ if value.is_a?(Record)
8
+ value = {_id: value.id}
9
+ end
10
+ cleaned[key] = value
11
+ end
12
+
13
+ Task.add_task(:deliver_email, cleaned, site)
14
+ end
15
+
16
+ def perform_delivery(options)
17
+ mail = Mail.new
18
+
19
+ # retrieve records that were replaced with their ID as above
20
+ options.each do |key, value|
21
+ if value.is_a?(Hash) && value.key?('_id')
22
+ options[key] = site.records.find(value['_id'])
23
+ end
24
+ end
25
+
26
+ # email headers
27
+ %w{to from cc bcc subject}.each do |param|
28
+ options[param] ||= options[param.to_sym] || self.send(param)
29
+ mail.send("#{param}=", options[param])
30
+ end
31
+ render_binding = binding
32
+
33
+ # rendered text body
34
+ unless text_body.blank?
35
+ text_body = self.text_body
36
+ text_part = Mail::Part.new do
37
+ body Ember::Template.new(text_body).render(render_binding)
38
+ end
39
+ mail.text_part = text_part
40
+ end
41
+
42
+ # rendered html body
43
+ unless html_body.blank?
44
+ html_body = self.html_body
45
+ # FIXME: reloading should be done elsewhere, not a concern of email
46
+ #Layout.reload_layouts(site) if Yodel.env.development?
47
+ if self.html_layout && layout = site.layouts.where(name: self.html_layout, mime_type: :html).first
48
+ @content = Ember::Template.new(html_body).render(render_binding)
49
+ @binding = render_binding
50
+ html_content = layout.render(self)
51
+ else
52
+ html_content = Ember::Template.new(html_body).render(render_binding)
53
+ end
54
+ html_part = Mail::Part.new do
55
+ content_type 'text/html; charset=UTF-8'
56
+ body html_content
57
+ end
58
+ mail.html_part = html_part
59
+ end
60
+
61
+ mail.deliver!
62
+ end
63
+
64
+ def content
65
+ @content
66
+ end
67
+
68
+ def set_content(content)
69
+ @content = content
70
+ end
71
+
72
+ def get_binding
73
+ @binding
74
+ end
75
+
76
+ def set_binding(binding)
77
+ @binding = binding
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ class RecordModelMigration < Migration
2
+ def self.up(site)
3
+ records = Model.new(site, name: 'Record')
4
+ records.modify do |records|
5
+ # identity, hierarchy and search
6
+ add_many :children, model: :record, foreign_key: 'parent', order: 'index asc', display: false
7
+ add_field :index, :integer, validations: {required: {}}, display: false
8
+ add_one :owner, model: :user, display: false
9
+ add_field :name, :string
10
+ add_field :show_in_search, :boolean, default: true, section: 'Options'
11
+ add_field :search_keywords, :array, of: :string, display: false
12
+ add_field :search_title, :string, display: false
13
+
14
+ # modelling
15
+ add_one :eigenmodel, model: :model, destroy: true, display: false
16
+ add_one :parent, model: :record, index: true, display: false
17
+ add_one :model, index: true, display: false
18
+ records.descendants = [records]
19
+ end
20
+
21
+ site.model_types['records'] = records.id
22
+ site.model_plural_names['Record'] = 'records'
23
+ site.save
24
+ end
25
+
26
+ def self.down(site)
27
+ site.records.destroy
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ class PageModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :pages do |pages|
4
+ # core page attributes
5
+ add_field :permalink, :string, validations: {required: {}}, index: true, searchable: false, display: false
6
+ add_field :path, :string, validations: {required: {}}, index: true, searchable: false, display: false
7
+ add_field :created_at, :time, display: false
8
+ add_field :title, :string, validations: {required: {}}
9
+ add_field :content, :html
10
+
11
+ # options section
12
+ add_field :show_in_menus, :boolean, default: true, section: 'Options'
13
+ add_field :description, :text, section: 'Options', searchable: false
14
+ add_field :keywords, :text, section: 'Options', searchable: false
15
+ add_field :custom_meta_tags, :text, section: 'Options', searchable: false
16
+ add_one :new_child_page, model: :page, section: 'Options', show_blank: true, blank_text: 'None'
17
+
18
+ # layout
19
+ add_field :page_layout, :string, section: 'Options', default: nil, searchable: false
20
+ add_one :page_layout_record, model: :layout, display: false
21
+ add_field :edit_layout, :string, searchable: false, section: 'Options'
22
+ add_one :edit_layout_record, model: :layout, display: false
23
+
24
+ add_field :name, :alias, of: :title
25
+ pages.default_child_model = pages.id
26
+ pages.allowed_children = [pages]
27
+ pages.allowed_parents = [pages]
28
+ pages.record_class_name = 'Page'
29
+ end
30
+
31
+ # glob pages are normal pages, but match paths with components at the end
32
+ # of the page's path, e.g /git/HEAD would match the glob page /git
33
+ site.pages.create_model :glob_pages do |glob_pages|
34
+ end
35
+
36
+ # default root page
37
+ page = site.pages.new
38
+ page.title = "Home"
39
+ page.save
40
+ end
41
+
42
+ def self.down(site)
43
+ site.pages.destroy
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ class LayoutModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :layouts do |layouts|
4
+ add_field :name, :string, validations: {required: {}}, index: true
5
+ add_field :mime_type, :string, validations: {required: {}}, index: true
6
+
7
+ layouts.allowed_children = []
8
+ layouts.allowed_parents = []
9
+ layouts.searchable = false
10
+ layouts.record_class_name = 'Layout'
11
+ end
12
+
13
+ site.layouts.create_model :persistent_layouts do |persistent_layouts|
14
+ add_field :markup, :html, validations: {required: {}}
15
+ add_many :pages, foreign_key: 'page_layout_record'
16
+
17
+ persistent_layouts.allowed_children = [persistent_layouts]
18
+ persistent_layouts.allowed_parents = [persistent_layouts]
19
+ persistent_layouts.searchable = false
20
+ persistent_layouts.record_class_name = 'PersistentLayout'
21
+ end
22
+
23
+ site.layouts.create_model :file_layouts do |file_layouts|
24
+ add_field :path, :string, validations: {required: {}}
25
+
26
+ file_layouts.allowed_children = [file_layouts]
27
+ file_layouts.allowed_parents = [file_layouts]
28
+ file_layouts.searchable = false
29
+ file_layouts.record_class_name = 'FileLayout'
30
+ end
31
+ end
32
+
33
+ def self.down(site)
34
+ site.layouts.destroy
35
+ site.persistent_layouts.destroy
36
+ site.file_layouts.destroy
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ class GroupModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :groups do |groups|
4
+ add_field :name, :string, validations: {required: {}}
5
+ add_many :users, store: false
6
+ groups.icon = '/admin/images/group_icon.png'
7
+ groups.record_class_name = 'Group'
8
+ end
9
+ site.reload
10
+
11
+ # a special singleton group representing an 'owner' of a record
12
+ site.groups.create_model :owner_groups do |group|
13
+ group.record_class_name = 'OwnerGroup'
14
+ end
15
+ site.reload
16
+
17
+ # a special singleton group representing no one
18
+ site.groups.create_model :noone_groups do |group|
19
+ group.record_class_name = 'NooneGroup'
20
+ end
21
+ site.reload
22
+
23
+ # a special singleton group representing 'everyone'
24
+ site.groups.create_model :guest_groups do |group|
25
+ group.record_class_name = 'GuestsGroup'
26
+ end
27
+ site.reload
28
+
29
+
30
+ # permissions are based on a hierarchy of groups. branches are permitted.
31
+ noone = site.noone_groups.new(name: 'No One')
32
+ noone.save
33
+
34
+ devs = site.groups.new(name: 'Developers')
35
+ devs.parent = noone
36
+ devs.save
37
+
38
+ admins = site.groups.new(name: 'Administrators')
39
+ admins.parent = devs
40
+ admins.save
41
+
42
+ owner = site.owner_groups.new(name: 'Owner')
43
+ owner.parent = admins
44
+ owner.save
45
+
46
+ users = site.groups.new(name: 'Users')
47
+ users.parent = owner
48
+ users.save
49
+
50
+ guests = site.guest_groups.new(name: 'Guests')
51
+ guests.parent = users
52
+ guests.save
53
+ end
54
+
55
+ def self.down(site)
56
+ site.groups.destroy
57
+ site.owner_groups.destroy
58
+ site.noone_groups.destroy
59
+ site.guest_groups.destroy
60
+ end
61
+ end