yodel_development_environment 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/Rakefile +1 -0
  4. data/lib/layouts/common.html +25 -0
  5. data/lib/layouts/common/default_user.html +7 -0
  6. data/lib/layouts/common/remote.html +13 -0
  7. data/lib/layouts/common/remotes_page.html +33 -0
  8. data/lib/layouts/common/setup.html +10 -0
  9. data/lib/layouts/common/setup_remote.html +12 -0
  10. data/lib/layouts/common/site.html +9 -0
  11. data/lib/layouts/common/sites_page.html +75 -0
  12. data/lib/migrations/site/01_sites_page_model.rb +11 -0
  13. data/lib/migrations/site/02_remotes_page_model.rb +11 -0
  14. data/lib/migrations/site/03_default_user_model.rb +13 -0
  15. data/lib/migrations/site/04_page_structure.rb +68 -0
  16. data/lib/migrations/yodel/01_record_model.rb +29 -0
  17. data/lib/migrations/yodel/02_page_model.rb +40 -0
  18. data/lib/migrations/yodel/03_layout_model.rb +38 -0
  19. data/lib/migrations/yodel/04_group_model.rb +61 -0
  20. data/lib/migrations/yodel/05_user_model.rb +23 -0
  21. data/lib/migrations/yodel/06_snippet_model.rb +13 -0
  22. data/lib/migrations/yodel/07_search_page_model.rb +32 -0
  23. data/lib/migrations/yodel/08_default_site_options.rb +21 -0
  24. data/lib/migrations/yodel/09_security_page_models.rb +36 -0
  25. data/lib/migrations/yodel/10_record_proxy_page_model.rb +17 -0
  26. data/lib/migrations/yodel/11_email_model.rb +28 -0
  27. data/lib/migrations/yodel/12_api_call_model.rb +23 -0
  28. data/lib/migrations/yodel/13_redirect_page_model.rb +13 -0
  29. data/lib/migrations/yodel/14_menu_model.rb +20 -0
  30. data/lib/models/development_sites_page.rb +177 -0
  31. data/lib/models/remotes_page.rb +42 -0
  32. data/lib/public/css/screen.css +59 -0
  33. data/lib/public/js/main.js +18 -0
  34. data/lib/site_template/attachments/.gitkeep +0 -0
  35. data/lib/site_template/gitignore +2 -0
  36. data/lib/site_template/layouts/page.html +18 -0
  37. data/lib/site_template/layouts/page/.gitkeep +0 -0
  38. data/lib/site_template/migrations/extensions/.gitkeep +0 -0
  39. data/lib/site_template/migrations/site/.gitkeep +0 -0
  40. data/lib/site_template/partials/.gitkeep +0 -0
  41. data/lib/site_template/public/.gitkeep +0 -0
  42. data/lib/site_template/public/css/main.css +1 -0
  43. data/lib/site_template/public/js/main.js +1 -0
  44. data/lib/yodel_development_environment.rb +3 -0
  45. data/yodel_development_environment.gemspec +22 -0
  46. metadata +90 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ lib/site.yml
6
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yodel_development_environment.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <link rel="stylesheet" href="/core/css/reset.css" type="text/css">
5
+ <link rel="stylesheet" href="/core/css/core.css" type="text/css">
6
+ <link rel="stylesheet" href="/css/screen.css" type="text/css">
7
+ <script src="/core/js/jquery.min.js"></script>
8
+ <script src="/core/js/json2.js"></script>
9
+ <script src="/core/js/yodel_jquery.js"></script>
10
+ <script src="/js/main.js"></script>
11
+ <title>Yodel</title>
12
+ </head>
13
+ <body>
14
+ <article>
15
+ <header>
16
+ <div id="lip"></div>
17
+ <h1>yodel</h1>
18
+ <% if site.default_users.count > 0 %>
19
+ <%= menu :nav %>
20
+ <% end %>
21
+ </header>
22
+ <%= content %>
23
+ </article>
24
+ </body>
25
+ </html>
@@ -0,0 +1,7 @@
1
+ <h1>Default User</h1>
2
+ <% form_for site.default_users.first, remote: true do |form| %>
3
+ <%= form.field_row :name %>
4
+ <%= form.field_row :email %>
5
+ <%= form.field_row :password %>
6
+ <input type="submit" value="Save">
7
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <h1><%= record.name %></h1>
2
+
3
+ <% form_for record, remote: true do |form| %>
4
+ <%= form.field_row :name %>
5
+ <%= form.field_row :url %>
6
+ <%= form.field_row :username %>
7
+ <%= form.field_row :password %>
8
+ <input type="submit" value='Save'>
9
+ <button onclick="window.location = '/remotes'">Cancel</button>
10
+ <% form.success do %>
11
+ window.location = '/remotes';
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <ul>
2
+ <% if Remote.count > 0 %>
3
+ <% Remote.all.each do |remote| %>
4
+ <li>
5
+ <a href="<%= path %>?id=<%= remote.id %>"><%= remote.name %></a>
6
+ <%= delete_link 'Delete', wrap: ["<input type='hidden' name='id' value='#{remote.id}'>", nil], class: 'action', confirm: 'Are you sure you want to delete this remote?' %>
7
+ </li>
8
+ <% end %>
9
+ <% else %>
10
+ <li>No remotes</li>
11
+ <% end %>
12
+ </ul>
13
+ <a class="action" href="#" id="add_remote">Add Remote</a>
14
+
15
+ <div id="new_remote" style="display: none; padding-top: 30px; padding-bottom: 30px">
16
+ <% form_for_new_record remote: true do |form| %>
17
+ <%= form.field_row :name %>
18
+ <%= form.field_row :url %>
19
+ <%= form.field_row :username %>
20
+ <%= form.field_row :password %>
21
+ <input type="submit" value='Add'>
22
+ <% form.success do %>
23
+ window.location = '/remotes';
24
+ <% end %>
25
+ <% end %>
26
+ </div>
27
+
28
+ <script>
29
+ $('#add_remote').click(function(event) {
30
+ $('#new_remote').slideToggle();
31
+ event.preventDefault();
32
+ })
33
+ </script>
@@ -0,0 +1,10 @@
1
+ <p>All Yodel sites have an admin panel which requires a username and password to access. Enter the details you would like to use to login. You can change these details on a site by site basis, and for all new sites by visiting setup again.</p>
2
+ <% page('/default-user').form_for_new_record remote: true do |form| %>
3
+ <%= form.field_row :name %>
4
+ <%= form.field_row :email %>
5
+ <%= form.field_row :password %>
6
+ <input type="submit" value="Save">
7
+ <% form.success do %>
8
+ window.location = '/setup-remote';
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <p>You can publish your sites to a remote Yodel server by entering your details below. You can skip this step and add a remote server later.</p>
2
+ <% page('/remotes').form_for Remote.new(name: 'Yodel CMS', url: 'http://yodelcms.com/', username: site.default_users.first.email), remote: true do |form| %>
3
+ <%= form.field_row :name %>
4
+ <%= form.field_row :url %>
5
+ <%= form.field_row :username %>
6
+ <%= form.field_row :password %>
7
+ <input type="submit" value="Save">
8
+ <% form.success do %>
9
+ window.location = '/sites';
10
+ <% end %>
11
+ <% end %>
12
+ <a href="/sites" class="action">Skip to Sites</a>
@@ -0,0 +1,9 @@
1
+ <h1><%= record.name %></h1>
2
+ <p>Enter the root directory for this site. The previous root directory was:<br><%= record.root_directory %></p>
3
+ <% form_for record, remote: true do |form| %>
4
+ <%= form.field_row :root_directory %>
5
+ <input type="submit" value="Save">
6
+ <% form.success do %>
7
+ window.location = 'http://<%= record.domains.first %><% if request.port != 80 %>:<%= request.port %><% end %>';
8
+ <% end %>
9
+ <% end %>
@@ -0,0 +1,75 @@
1
+ <h1>Local Sites</h1>
2
+ <ul>
3
+ <% if Site.all.count == 1 %>
4
+ <li>No sites</li>
5
+ <% else %>
6
+ <% Site.all.each do |site| %>
7
+ <% next if site.name == 'yodel' %>
8
+ <li class="site" data-site-id="<%= site.id %>">
9
+ <a href="http://<%= site.domains.first %><% if request.port != 80 %>:<%= request.port %><% end %>/"><%= site.name %></a>
10
+ <a class="action" href="http://<%= site.domains.first %><% if request.port != 80 %>:<%= request.port %><% end %>/admin/">Visit Admin</a>
11
+ <%= delete_link 'Delete', wrap: ["<input type='hidden' name='id' value='#{site.id}'>", nil], class: 'action', confirm: 'Are you sure you want to delete this site? Unless you have deployed it to a remote server, you will not be able to recover it.' %>
12
+ </li>
13
+ <% end %>
14
+ <% end %>
15
+ </ul>
16
+ <h1>Remote Sites</h1>
17
+ <ul class="remotes">
18
+ <% if Remote.count > 0 %>
19
+ <% Remote.all.each do |remote| %>
20
+ <li data-remote="<%= remote.id %>" data-remote-name="<%= remote.name %>">Loading <%= remote.name %>... <img src="/core/images/spinner.gif"></li>
21
+ <% end %>
22
+ <% else %>
23
+ <li>No remote sites</li>
24
+ <% end %>
25
+ </ul>
26
+
27
+ <a class="action" href="#" id="add_site">Create Site</a>
28
+
29
+ <div id="new_site" style="<% if flash[:error].nil? %>display: none; <% end %>padding-top: 30px; padding-bottom: 30px">
30
+ <form action="/sites" method="post">
31
+ <div>
32
+ <label>Name</label>
33
+ <div>
34
+ <input type="text" name="name"> <input type="submit" value='Create'>
35
+ <% if flash[:error] %>
36
+ <span class="yodel-form-activity invalid"><%= flash[:error] %></span>
37
+ <% end %>
38
+ </div>
39
+ </div>
40
+ </form>
41
+ </div>
42
+
43
+ <script>
44
+ $('#add_site').click(function(event) {
45
+ $('#new_site').slideToggle();
46
+ event.preventDefault();
47
+ });
48
+
49
+ var sitesRemaining = $('li[data-remote]').length;
50
+ var sites = 0;
51
+ // FIXME: need error handler here
52
+ $('li[data-remote]').each(function(index, row) {
53
+ row = $(row);
54
+ $.ajax('/remotes.json?id=' + row.attr('data-remote'), {dataType: 'json', success: function(data) {
55
+ if(data.success) {
56
+ for(var i = 0; i < data.sites.length; i++) {
57
+ var site = data.sites[i];
58
+ if($('li[data-site-id=' + site.id + ']').length == 0) {
59
+ $('.remotes').append("<li class='remote_site'>" + site.name + " <span class='remote_name'>(" + row.attr('data-remote-name') + ")</span><a href='#' data-remote='" + row.attr('data-remote') + "' data-remote-id='" + site.id + "' data-site-name='" + site.name + "' class='action'>Clone</a></li>");
60
+ sites += 1;
61
+ }
62
+ }
63
+ row.remove();
64
+ } else {
65
+ row.html(row.attr('data-remote-name') + ": " + data.reason);
66
+ }
67
+ }, complete: function() {
68
+ sitesRemaining -= 1;
69
+ if(sitesRemaining == 0 && sites == 0) {
70
+ $('.remotes').append("<li>No uncloned remote sites</li>");
71
+ }
72
+ }});
73
+ });
74
+ </script>
75
+
@@ -0,0 +1,11 @@
1
+ class SitesPageModelMigration < Migration
2
+ def self.up(site)
3
+ site.record_proxy_pages.create_model :sites_page do |sites_pages|
4
+ sites_pages.record_class_name = 'DevelopmentSitesPage'
5
+ end
6
+ end
7
+
8
+ def self.down(site)
9
+ site.sites_pages.destroy
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RemoteModelMigration < Migration
2
+ def self.up(site)
3
+ site.record_proxy_pages.create_model :remotes_page do |remotes_pages|
4
+ remotes_pages.record_class_name = 'RemotesPage'
5
+ end
6
+ end
7
+
8
+ def self.down(site)
9
+ sites.remotes_pages.destroy
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class DefaultUserModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :default_user do |default_users|
4
+ add_field :name, :string, validations: {required: {}}
5
+ add_field :email, :email, validations: {required: {}}
6
+ add_field :password, :password, validations: {required: {}}
7
+ end
8
+ end
9
+
10
+ def self.down(site)
11
+ site.default_users.destroy
12
+ end
13
+ end
@@ -0,0 +1,68 @@
1
+ class PageStructureMigration < Migration
2
+ def self.up(site)
3
+ # remove the default home page
4
+ home = site.pages.where(path: '/').first
5
+ home.destroy
6
+
7
+ # home redirects to sites
8
+ home = site.redirect_pages.new
9
+ home.title = 'Home'
10
+ home.url = '/sites'
11
+ home.save
12
+
13
+ # sites
14
+ sites = site.sites_pages.new
15
+ sites.title = 'Sites'
16
+ sites.parent = home
17
+ sites.show_record_layout = 'site'
18
+ sites.save
19
+
20
+ # remotes
21
+ remotes = site.remotes_pages.new
22
+ remotes.title = 'Remotes'
23
+ remotes.parent = home
24
+ remotes.show_record_layout = 'remote'
25
+ remotes.save
26
+
27
+ # default user
28
+ default_user = site.record_proxy_pages.new
29
+ default_user.title = 'Default User'
30
+ default_user.parent = home
31
+ default_user.record_model = site.default_users
32
+ default_user.page_layout = 'default_user'
33
+ default_user.after_update_page = default_user
34
+ default_user.save
35
+
36
+ # second setup step (done first because the first step refns this)
37
+ setup_two = site.pages.new
38
+ setup_two.title = 'Setup Remote'
39
+ setup_two.parent = home
40
+ setup_two.page_layout = 'setup_remote'
41
+ setup_two.save
42
+
43
+ # initial setup
44
+ setup = site.pages.new
45
+ setup.title = 'Setup'
46
+ setup.parent = home
47
+ setup.page_layout = 'setup'
48
+ setup.save
49
+
50
+ # menu
51
+ nav = site.menus.new
52
+ nav.name = 'nav'
53
+ nav.root = home
54
+ s1 = nav.exceptions.new
55
+ s1.page = setup
56
+ s1.show = false
57
+ s1.save
58
+ s2 = nav.exceptions.new
59
+ s2.page = setup_two
60
+ s2.show = false
61
+ s2.save
62
+ nav.save
63
+ end
64
+
65
+ def self.down(site)
66
+ site.pages.all.each(&:destroy)
67
+ end
68
+ 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,40 @@
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
+ # default root page
32
+ page = site.pages.new
33
+ page.title = "Home"
34
+ page.save
35
+ end
36
+
37
+ def self.down(site)
38
+ site.pages.destroy
39
+ end
40
+ 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
@@ -0,0 +1,23 @@
1
+ class UserModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :users do |users|
4
+ add_field :first_name, :string
5
+ add_field :last_name, :string
6
+ add_field :email, :email, validations: {required: {}, unique: {}}, searchable: false
7
+ add_field :oauth_id, :string, index: true, searchable: false
8
+ add_field :username, :string, index: true, validations: {required: {}, unique: {}}, searchable: false
9
+ add_field :password, :password, validations: {required: {}}, searchable: false
10
+ add_field :password_salt, :string, display: false, searchable: false
11
+ add_many :groups, default: [site.groups['Users'].id]
12
+ add_field :owner, :self
13
+
14
+ add_field :name, :function, fn: 'format("{{first_name}} {{last_name}}").strip()'
15
+ users.icon = '/admin/images/user_icon.png'
16
+ users.record_class_name = 'User'
17
+ end
18
+ end
19
+
20
+ def self.down(site)
21
+ site.users.destroy
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ class SnippetModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :snippets do |snippets|
4
+ add_field :name, :string, validations: {required: {}}, index: true
5
+ add_field :content, :text
6
+ snippets.searchable = false
7
+ end
8
+ end
9
+
10
+ def self.down(site)
11
+ site.snippets.destroy
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ class SearchPageModelMigration < Migration
2
+ def self.up(site)
3
+ operators = [ 'Equals', 'Not Equal', 'Greater Than',
4
+ 'Less Than', 'Greater Than or Equal To',
5
+ 'Less Than or Equal To', 'In']
6
+
7
+ site.pages.create_model :search_pages do |search_pages|
8
+ add_field :sort, :string, searchable: false
9
+ add_field :limit, :integer
10
+ add_field :skip, :integer
11
+ add_one :type, model: :model
12
+
13
+ add_embed_many :conditions do
14
+ add_field :name, :string
15
+ add_field :value, :string
16
+ add_field :operator, :enum, options: operators
17
+ end
18
+
19
+ add_embed_many :user_conditions, default: [{name: 'search_keywords', as: 'query', operator: 'In'}] do
20
+ add_field :name, :string
21
+ add_field :as, :string
22
+ add_field :operator, :enum, options: operators
23
+ end
24
+
25
+ search_pages.record_class_name = 'SearchPage'
26
+ end
27
+ end
28
+
29
+ def self.down(site)
30
+ site.search_pages.destroy
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ class DefaultSiteOptionsMigration < Migration
2
+ def self.up(site)
3
+ site.options = {
4
+ pages: {
5
+ permalink_character: {
6
+ description: 'When Yodel creates a URL for a page by using the title of the page, there are sometimes characters (such as spaces) that need to be replaced. This character will be used in their place. e.g "About Us" would become "about-us".',
7
+ type: 'String',
8
+ default: '-',
9
+ value: '-'
10
+ }
11
+ },
12
+ icon: nil
13
+ }
14
+ site.save
15
+ end
16
+
17
+ def self.down(site)
18
+ site.options = {}
19
+ site.save
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ class SecurityPageModelsMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :login_pages do |login_pages|
4
+ add_field :username_field, :string, validations: {required: {}}, default: 'username'
5
+ add_field :password_field, :string, validations: {required: {}}, default: 'password'
6
+ add_one :redirect_to, model: :page
7
+ login_pages.record_class_name = 'LoginPage'
8
+ end
9
+
10
+ site.pages.create_model :logout_pages do |logout_pages|
11
+ add_one :redirect_to, model: :page
12
+ logout_pages.record_class_name = 'LogoutPage'
13
+ end
14
+
15
+ site.pages.create_model :password_reset_pages do |password_reset_pages|
16
+ add_field :success, :html, default: 'Thank you, your password has been emailed to your email address.'
17
+ add_field :email_field, :string, validations: {required: {}}, default: 'email'
18
+ password_reset_pages.record_class_name = 'PasswordResetPage'
19
+ end
20
+
21
+ site.pages.create_model :facebook_login_pages do |facebook_login_pages|
22
+ add_field :callback_uri, :string
23
+ add_field :app_id, :string
24
+ add_field :app_secret, :string
25
+ add_one :join_page, model: :page
26
+ add_one :after_login_page, model: :page
27
+ facebook_login_pages.record_class_name = 'FacebookLoginPage'
28
+ end
29
+ end
30
+
31
+ def self.down(site)
32
+ site.login_pages.destroy
33
+ site.logout_pages.destroy
34
+ site.password_reset_pages.destroy
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ class RecordProxyPageModelMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :record_proxy_pages do |record_proxy_pages|
4
+ add_one :record_model, model: :model
5
+ add_one :after_create_page, model: :page
6
+ add_one :after_delete_page, model: :page
7
+ add_one :after_update_page, model: :page
8
+ add_field :show_record_layout, :string
9
+ add_one :show_record_layout_record, model: :layout, display: false
10
+ record_proxy_pages.record_class_name = 'RecordProxyPage'
11
+ end
12
+ end
13
+
14
+ def self.down(site)
15
+ site.record_proxy_pages.destroy
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ class EmailModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :emails do |emails|
4
+ add_field :name, :string
5
+ add_field :from, :string
6
+ add_field :to, :string
7
+ add_field :cc, :string
8
+ add_field :bcc, :string
9
+ add_field :subject, :string
10
+ add_field :text_body, :text
11
+ add_field :html_body, :html
12
+ add_field :html_layout, :string
13
+ emails.record_class_name = 'Email'
14
+ end
15
+
16
+ # template password reset email
17
+ password_reset_email = site.emails.new
18
+ password_reset_email.name = 'password_reset'
19
+ password_reset_email.from = 'admin@site.com'
20
+ password_reset_email.subject = 'Password Reset'
21
+ password_reset_email.text_body = 'Hi <%= options["first_name"] %>, your password has been reset and is now: <%= options["new_password"] %>.'
22
+ password_reset_email.save
23
+ end
24
+
25
+ def self.down(site)
26
+ site.emails.destroy
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ class APICallModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :api_calls do |api_calls|
4
+ add_field :name, :string
5
+ add_field :http_method, :string
6
+ add_field :domain, :string
7
+ add_field :port, :integer, default: 80
8
+ add_field :path, :string
9
+ add_field :username, :string
10
+ add_field :password, :string
11
+ add_field :authentication, :enum, options: %w{basic digest}
12
+ add_field :mime_type, :string, default: 'json'
13
+ add_field :body, :text
14
+ add_field :body_layout, :string
15
+ add_field :function, :string
16
+ api_calls.record_class_name = 'APICall'
17
+ end
18
+ end
19
+
20
+ def self.down(site)
21
+ site.api_calls.destroy
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ class RedirectPageModelMigration < Migration
2
+ def self.up(site)
3
+ site.pages.create_model :redirect_page do |redirect_pages|
4
+ add_field :url, :string, searchable: false
5
+ add_one :page, show_blank: true, blank_text: 'None'
6
+ redirect_pages.record_class_name = 'RedirectPage'
7
+ end
8
+ end
9
+
10
+ def self.down(site)
11
+ site.redirect_pages.destroy
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ class MenuModelMigration < Migration
2
+ def self.up(site)
3
+ site.records.create_model :menu do |menus|
4
+ add_one :root, model: :page, validations: {required: {}}
5
+ add_field :include_root, :boolean, default: false
6
+ add_field :include_all_children, :boolean, default: true
7
+ add_field :depth, :integer, default: 0, validations: {required: {}}
8
+ add_embed_many :exceptions do
9
+ add_one :page
10
+ add_field :show, :boolean, default: false
11
+ add_field :depth, :integer, default: 0, validations: {required: {}}
12
+ end
13
+ menus.record_class_name = 'Menu'
14
+ end
15
+ end
16
+
17
+ def self.down(site)
18
+ site.menus.destroy
19
+ end
20
+ end
@@ -0,0 +1,177 @@
1
+ class DevelopmentSitesPage < RecordProxyPage
2
+ REMOTE_NAME = 'yodel'
3
+
4
+ # record proxy pages deal with site models (site.model_name). Override the methods it uses
5
+ # to interact with these models we can edit Sites (a non site model)
6
+ def record
7
+ @record ||= Site.find(BSON::ObjectId.from_string(params['id']))
8
+ end
9
+
10
+ def records
11
+ @records ||= Site.all
12
+ end
13
+
14
+ # a default user is required before any sites or remotes can be created
15
+ respond_to :get do
16
+ with :html do
17
+ if site.default_users.count == 0
18
+ response.redirect '/setup'
19
+ else
20
+ super()
21
+ end
22
+ end
23
+ end
24
+
25
+ # create a site
26
+ respond_to :post do
27
+ # clone a site from a remote server
28
+ with :json do
29
+ # ensure yodel is set up
30
+ default_user = site.default_users.first
31
+ return {success: false, reason: 'No default user has been created'} if site.default_users.count == 0
32
+
33
+ # extract the site name and generate an identifier to be used as the site's domain and folder name
34
+ site_name = params['name'].to_s
35
+ identifier = site_name.downcase.gsub(/[^a-z0-9]+/, '_')
36
+ return {success: false, reason: 'No site name provided'} if site_name.blank? || identifier.blank?
37
+
38
+ # find the remote to clone from
39
+ remote = Remote.find(BSON::ObjectId.from_string(params['remote']))
40
+ return {success: false, reason: 'Remote could not be found'} if remote.nil?
41
+
42
+ # construct the git url used to clone the site
43
+ remote_id = params['remote_id']
44
+ git_url = remote.git_url(remote_id)
45
+ return {success: false, reason: 'Git URL could not be constructed'} if git_url.blank?
46
+
47
+ # find an unused site folder name
48
+ site_folder = identifier
49
+ counter = 0
50
+ while File.exist?(File.join(Yodel.config.sites_root, site_folder))
51
+ counter += 1
52
+ site_folder = "#{identifier}_#{counter}"
53
+ end
54
+
55
+ # clone the repos locally
56
+ Dir.chdir(Yodel.config.sites_root) do
57
+ result = `#{Yodel.config.git_path} clone -o #{REMOTE_NAME} #{git_url} #{site_folder}`
58
+ return {success: false, reason: 'Git error: ' + $1} if result =~ /error: (.+)$/
59
+ end
60
+
61
+ # create a new site from the cloned site.yml file
62
+ site_yml = File.join(Yodel.config.sites_root, site_folder, Yodel::SITE_YML_FILE_NAME)
63
+ return {success: false, reason: 'Site yml file was not cloned successfully'} unless File.exist?(site_yml)
64
+ new_site = Site.load_from_site_yaml(site_yml)
65
+
66
+ # find an unused default local domain
67
+ domain = "#{identifier}.yodel"
68
+ counter = 0
69
+ while Site.exists?(domains: domain)
70
+ counter += 1
71
+ domain = "#{identifier}-#{counter}.yodel"
72
+ end
73
+
74
+ # add the remote and a new default local domain
75
+ new_site.domains.unshift(domain)
76
+ new_site.remote_id = remote_id
77
+ new_site.remote = remote
78
+ new_site.save
79
+
80
+ # initialise the site
81
+ Migration.run_migrations(new_site)
82
+ create_default_user(new_site, default_user)
83
+ {success: true, url: new_site_url(new_site)}
84
+ end
85
+
86
+ # create a new local site
87
+ with :html do
88
+ # the default user is required for the git repos, and creating an
89
+ # admin account in the new site
90
+ default_user = site.default_users.first
91
+ if default_user.nil?
92
+ response.redirect '/setup'
93
+ return
94
+ end
95
+
96
+ name = cleanse_name(params['name'].sub('.yodel', ''))
97
+ if name.blank?
98
+ flash[:error] = 'You must enter a name for this site'
99
+ response.redirect '/sites'
100
+ return
101
+ end
102
+
103
+ # create a new folder for the site
104
+ site_dir = File.join(Yodel.config.sites_root, name)
105
+ FileUtils.cp_r(File.join(File.dirname(__FILE__), '..', 'site_template'), site_dir)
106
+
107
+ # rename the gitignore file so it becomes active
108
+ FileUtils.mv(File.join(site_dir, 'gitignore'), File.join(site_dir, '.gitignore'))
109
+
110
+ # create the new site
111
+ new_site = Site.new
112
+ new_site.name = name
113
+ new_site.root_directory = site_dir
114
+ new_site.domains << "#{name}.yodel"
115
+
116
+ # copy core yodel migrations
117
+ yodel_migrations_dir = File.join(site_dir, Yodel::MIGRATIONS_DIRECTORY_NAME, Yodel::YODEL_MIGRATIONS_DIRECTORY_NAME)
118
+ FileUtils.cp_r(Yodel.config.yodel_migration_directory, yodel_migrations_dir)
119
+
120
+ # copy extension migrations
121
+ extension_migrations_dir = File.join(site_dir, Yodel::MIGRATIONS_DIRECTORY_NAME, Yodel::EXTENSION_MIGRATIONS_DIRECTORY_NAME)
122
+ Yodel.config.extensions.each do |extension|
123
+ FileUtils.cp_r(extension.migrations_dir, File.join(extension_migrations_dir, extension.name)) if File.directory?(extension.migrations_dir)
124
+ new_site.extensions << extension.name
125
+ end
126
+
127
+ # create the repository and perform the first commit
128
+ if Yodel.config.owner_user
129
+ if Yodel.config.owner_group != 0
130
+ FileUtils.chown_R(Yodel.config.owner_user, Yodel.config.owner_group, site_dir)
131
+ else
132
+ FileUtils.chown_R(Yodel.config.owner_user, nil, site_dir)
133
+ end
134
+ end
135
+ repos = Git.init(site_dir)
136
+ repos.config('user.name', default_user.name)
137
+ repos.config('user.email', default_user.email)
138
+ repos.config('http.postBuffer', (200 * 1024 * 1024))
139
+ repos.add([Yodel::LAYOUTS_DIRECTORY_NAME, Yodel::MIGRATIONS_DIRECTORY_NAME, Yodel::PARTIALS_DIRECTORY_NAME, Yodel::PUBLIC_DIRECTORY_NAME, Yodel::ATTACHMENTS_DIRECTORY_NAME])
140
+ repos.commit_all('New yodel site')
141
+
142
+ # save and initialise the site
143
+ new_site.save
144
+ Migration.run_migrations(new_site)
145
+ create_default_user(new_site, default_user)
146
+
147
+ # redirect to the new site
148
+ response.redirect new_site_url(new_site)
149
+ end
150
+ end
151
+
152
+ private
153
+ def cleanse_name(name)
154
+ name.downcase.gsub(/[^a-z0-9]+/, '_')
155
+ end
156
+
157
+ def create_default_user(new_site, default_user)
158
+ user = new_site.users.new
159
+ user.first_name = default_user.name
160
+ user.email = default_user.email
161
+ user.username = default_user.email
162
+ user.password = Password.hashed_password(nil, default_user.password)
163
+ user.groups << new_site.groups['Developers']
164
+ user.save
165
+
166
+ # because of the before_create callback, we need to override
167
+ # the salt and password manually by saving again
168
+ user.password_salt = nil
169
+ user.password = Password.hashed_password(nil, default_user.password)
170
+ user.save_without_validation
171
+ end
172
+
173
+ def new_site_url(new_site)
174
+ port = (request.port == 80 ? nil : request.port)
175
+ "http://#{new_site.domains.first}#{':' if port}#{port}/admin/pages"
176
+ end
177
+ end
@@ -0,0 +1,42 @@
1
+ class RemotesPage < RecordProxyPage
2
+ # record proxy pages deal with site models (site.model_name). Override the methods it uses
3
+ # to interact with these models we can edit Remotes (a non site model)
4
+ def record
5
+ @record ||= Remote.find(BSON::ObjectId.from_string(params['id']))
6
+ end
7
+
8
+ def records
9
+ @records ||= Remote.all
10
+ end
11
+
12
+ def new_record
13
+ Remote.new
14
+ end
15
+
16
+ def form_for_new_record(options={}, &block)
17
+ form_for(Remote.new, options, &block)
18
+ end
19
+
20
+ # a default user is required before any sites or remotes can be created
21
+ respond_to :get do
22
+ with :html do
23
+ if site.default_users.count == 0
24
+ response.redirect '/setup'
25
+ else
26
+ super()
27
+ end
28
+ end
29
+
30
+ with :json do
31
+ result = record.site_list
32
+
33
+ if result['success']
34
+ result['sites'] = result['sites'].reject do |remote_site|
35
+ Site.exists?(remote_id: remote_site['id'])
36
+ end
37
+ end
38
+
39
+ result
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ h2 {
2
+ margin-top: 30px;
3
+ }
4
+
5
+ ul {
6
+ border-top: 1px solid #eee;
7
+ color: #777;
8
+ }
9
+
10
+ ul li {
11
+ border-bottom: 1px solid #eee;
12
+ line-height: 35px;
13
+ }
14
+
15
+ .remotes li img {
16
+ position: relative;
17
+ top: 6px;
18
+ left: 20px;
19
+ }
20
+
21
+ a {
22
+ text-decoration: none;
23
+ }
24
+
25
+ a:hover {
26
+ text-decoration: underline;
27
+ }
28
+
29
+ a.action {
30
+ float: right;
31
+ font-size: 12px;
32
+ margin-left: 15px;
33
+ }
34
+
35
+ h1 + form, p + form {
36
+ margin-top: 20px;
37
+ }
38
+
39
+ #new_site form {
40
+ width: 600px;
41
+ }
42
+
43
+ #new_site div div {
44
+ width: 500px;
45
+ }
46
+
47
+ #new_site div div input[type=text] {
48
+ width: 300px;
49
+ }
50
+
51
+ .remote_name {
52
+ font-size: 11px;
53
+ color: #aaa;
54
+ }
55
+
56
+ .remote_site a.cloning {
57
+ background: center center url(/core/images/spinner.gif) no-repeat;
58
+ color: transparent;
59
+ }
@@ -0,0 +1,18 @@
1
+ $('.remote_site a').live('click', function(event) {
2
+ var link = $(this);
3
+ link.addClass('cloning');
4
+
5
+ jQuery.ajax('/sites.json', {data: {remote: link.attr('data-remote'), remote_id: link.attr('data-remote-id'), name: link.attr('data-site-name')}, type: 'POST', success: function(data) {
6
+ if(data.success)
7
+ if(data.url)
8
+ window.location = data.url;
9
+ else
10
+ alert("The site was created successfully, but no default url was returned. Please try refreshing this page, and visiting the site manually.");
11
+ else
12
+ alert("Sorry, an error occurred while cloninng this site: " + data.reason);
13
+ }, error: function(jqXHR, textStatus, errorThrown) {
14
+ alert("Sorry, an error occurred while cloning this site.");
15
+ }, complete: function(jqXHR, textStatus) {
16
+ link.removeClass('cloning');
17
+ }});
18
+ });
File without changes
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ attachments/*
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= page.title %></title>
5
+ <link rel='stylesheet' href='/core/css/reset.css' type='text/css'>
6
+ <link rel='stylesheet' href='/css/main.css' type='text/css'>
7
+ <meta name='description' content='<%= first_non_blank_response_to(:description) %>'>
8
+ <meta name='keywords' content='<%= first_non_blank_response_to(:keywords) %>'>
9
+ <%= first_non_blank_response_to(:custom_meta_tags) %>
10
+ <script src='/core/js/jquery.min.js'></script>
11
+ <script src='/core/js/json2.js'></script>
12
+ <script src='/core/js/yodel_jquery.js'></script>
13
+ <script src='/js/main.js'></script>
14
+ </head>
15
+ <body>
16
+ <%= content %>
17
+ </body>
18
+ </html>
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1 @@
1
+ /* site css */
@@ -0,0 +1 @@
1
+ /* site js */
@@ -0,0 +1,3 @@
1
+ module YodelDevelopmentEnvironment
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'yodel_development_environment'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'yodel_development_environment'
7
+ s.version = YodelDevelopmentEnvironment::VERSION
8
+ s.authors = ['Will Cannings']
9
+ s.email = ['me@willcannings.com']
10
+ s.homepage = 'http://yodelcms.com'
11
+ s.summary = 'Yodel Development Environment'
12
+ s.description = 'Yodel Development Environment'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ # specify any dependencies here; for example:
20
+ # s.add_development_dependency "rspec"
21
+ # s.add_runtime_dependency "rest-client"
22
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yodel_development_environment
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Will Cannings
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-09 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Yodel Development Environment
15
+ email:
16
+ - me@willcannings.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Rakefile
24
+ - lib/layouts/common.html
25
+ - lib/layouts/common/default_user.html
26
+ - lib/layouts/common/remote.html
27
+ - lib/layouts/common/remotes_page.html
28
+ - lib/layouts/common/setup.html
29
+ - lib/layouts/common/setup_remote.html
30
+ - lib/layouts/common/site.html
31
+ - lib/layouts/common/sites_page.html
32
+ - lib/migrations/site/01_sites_page_model.rb
33
+ - lib/migrations/site/02_remotes_page_model.rb
34
+ - lib/migrations/site/03_default_user_model.rb
35
+ - lib/migrations/site/04_page_structure.rb
36
+ - lib/migrations/yodel/01_record_model.rb
37
+ - lib/migrations/yodel/02_page_model.rb
38
+ - lib/migrations/yodel/03_layout_model.rb
39
+ - lib/migrations/yodel/04_group_model.rb
40
+ - lib/migrations/yodel/05_user_model.rb
41
+ - lib/migrations/yodel/06_snippet_model.rb
42
+ - lib/migrations/yodel/07_search_page_model.rb
43
+ - lib/migrations/yodel/08_default_site_options.rb
44
+ - lib/migrations/yodel/09_security_page_models.rb
45
+ - lib/migrations/yodel/10_record_proxy_page_model.rb
46
+ - lib/migrations/yodel/11_email_model.rb
47
+ - lib/migrations/yodel/12_api_call_model.rb
48
+ - lib/migrations/yodel/13_redirect_page_model.rb
49
+ - lib/migrations/yodel/14_menu_model.rb
50
+ - lib/models/development_sites_page.rb
51
+ - lib/models/remotes_page.rb
52
+ - lib/public/css/screen.css
53
+ - lib/public/js/main.js
54
+ - lib/site_template/attachments/.gitkeep
55
+ - lib/site_template/gitignore
56
+ - lib/site_template/layouts/page.html
57
+ - lib/site_template/layouts/page/.gitkeep
58
+ - lib/site_template/migrations/extensions/.gitkeep
59
+ - lib/site_template/migrations/site/.gitkeep
60
+ - lib/site_template/partials/.gitkeep
61
+ - lib/site_template/public/.gitkeep
62
+ - lib/site_template/public/css/main.css
63
+ - lib/site_template/public/js/main.js
64
+ - lib/yodel_development_environment.rb
65
+ - yodel_development_environment.gemspec
66
+ homepage: http://yodelcms.com
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.10
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Yodel Development Environment
90
+ test_files: []