diff options
| author | Paul Duncan <pabs@pablotron.org> | 2016-07-19 03:41:16 -0400 | 
|---|---|---|
| committer | Paul Duncan <pabs@pablotron.org> | 2016-07-19 03:41:16 -0400 | 
| commit | 15cccb43865cb8b78bd99253af976ff6366ef097 (patch) | |
| tree | 94b51cbbc2405878509ca558c7be336e5223fdba | |
| parent | f442f7a57b3b6b988f1d914b4ef5c53993b1fa90 (diff) | |
| download | guff-15cccb43865cb8b78bd99253af976ff6366ef097.tar.xz guff-15cccb43865cb8b78bd99253af976ff6366ef097.zip  | |
add themes, switch pages and sites to use them
| -rw-r--r-- | data/assets/js/admin/dialogs/page-edit.js | 11 | ||||
| -rw-r--r-- | data/init.yaml | 243 | ||||
| -rw-r--r-- | src/guff/apis.cr | 3 | ||||
| -rw-r--r-- | src/guff/model-set.cr | 1 | ||||
| -rw-r--r-- | src/guff/models/page.cr | 41 | ||||
| -rw-r--r-- | src/guff/models/site.cr | 1 | ||||
| -rw-r--r-- | src/guff/models/theme.cr | 102 | ||||
| -rw-r--r-- | src/guff/views/admin-page.cr | 17 | ||||
| -rw-r--r-- | src/views/admin-page.ecr | 37 | 
9 files changed, 355 insertions, 101 deletions
diff --git a/data/assets/js/admin/dialogs/page-edit.js b/data/assets/js/admin/dialogs/page-edit.js index 05d69a7..95c9a63 100644 --- a/data/assets/js/admin/dialogs/page-edit.js +++ b/data/assets/js/admin/dialogs/page-edit.js @@ -6,17 +6,20 @@ jQuery(function($) {    $(p + 'dialog').on('guff.loaded', function(ev) {      var r = ev.post_data; -    $(p + 'layout a').removeClass('btn-primary').addClass('btn-default'); -    $(p + 'layout a[data-val="' + r.layout + '"]') -      .toggleClass('btn-primary btn-default'); +    $(p + 'theme').val(r.theme_id || 'site-default');    });    $(p + 'confirm').click(function() { +    var theme_id = $(p + 'theme').val(); +    if (theme_id == 'site-default') +      theme_id = null; +      $(p + 'dialog').trigger({        type: 'guff.save',        post_data: { -        layout: $(p + 'layout .btn-primary').data('val'), +        have_theme_id: 't', +        theme_id: theme_id,        },      }); diff --git a/data/init.yaml b/data/init.yaml index 7e177c5..e11f82b 100644 --- a/data/init.yaml +++ b/data/init.yaml @@ -1,34 +1,6 @@  ---  init_sql:    - | -    CREATE TABLE sites ( -      site_id     INTEGER PRIMARY KEY, - -      name        TEXT UNIQUE NOT NULL -                  CHECK (LENGTH(name) > 0), - -      is_active   BOOLEAN NOT NULL DEFAULT false, - -      is_default  BOOLEAN NOT NULL DEFAULT false -    ) - -  - | -    INSERT INTO sites(site_id, name, is_active, is_default) VALUES -      (1, 'default', 1, 1) - -  - | -    CREATE TABLE site_domains ( -      site_id     INTEGER NOT NULL -                  REFERENCES sites(site_id), - -      domain      TEXT UNIQUE NOT NULL CHECK ( -                    LENGTH(domain) > 0 AND -                    domain = LOWER(domain) AND -                    domain NOT LIKE '% %' -                  ) -    ) - -  - |      CREATE TABLE roles (        role_id     INTEGER PRIMARY KEY, @@ -64,12 +36,196 @@ init_sql:                      email LIKE '%@%'                    ), +      created_at  TIMESTAMP WITH TIME ZONE NOT NULL +                  DEFAULT CURRENT_TIMESTAMP, +        password    TEXT NOT NULL DEFAULT '',        is_active   BOOLEAN NOT NULL DEFAULT false      )    - | +    CREATE TABLE themes ( +      theme_id   INTEGER PRIMARY KEY, + +      -- generated (by guff) url component of theme +      theme_slug    TEXT UNIQUE NOT NULL CHECK ( +                      LENGTH(theme_slug) > 0 AND +                      theme_slug NOT LIKE '% %' AND +                      theme_slug = LOWER(theme_slug) +                    ), + +      -- name +      theme_name    TEXT NOT NULL +                    CHECK (LENGTH(theme_name) > 0), + +      -- version +      theme_version TEXT NOT NULL +                    CHECK (LENGTH(theme_version) > 0), + +      -- theme release date +      theme_date    DATE NOT NULL, + +      -- sha1 digest of theme file +      theme_hash    TEXT NOT NULL CHECK ( +                      LENGTH(theme_hash) > 0 AND +                      theme_hash NOT LIKE '% %' AND +                      theme_hash = LOWER(theme_hash) +                    ), + +      is_system     BOOLEAN NOT NULL DEFAULT false +    ) + +  - | +    INSERT INTO themes( +      theme_id, +      theme_slug, +      theme_name, +      theme_version, +      theme_date, +      theme_hash, +      is_system +    ) VALUES +      (1, 'default', 'Default', '1.0', '2016-07-18', 'n/a', 1), +      (2, 'blank', 'Blank', '1.0', '2016-07-18', 'n/a', 1) + +  - | +    CREATE TABLE theme_data_types ( +      type_id     INTEGER PRIMARY KEY, + +      name        TEXT UNIQUE NOT NULL CHECK ( +                    LENGTH(name) > 0 AND +                    name NOT LIKE '% %' AND +                    name = LOWER(name) +                  ) +    ) + +  - | +    INSERT INTO theme_data_types(type_id, name) VALUES +      (1, 'metadata'), +      (2, 'template') + +  - | +    CREATE TABLE theme_data ( +      -- theme that data entry is associated with +      theme_id    INTEGER NOT NULL +                  REFERENCES themes(theme_id), + +      type_id     INTEGER NOT NULL +                  REFERENCES theme_data_types(type_id), + +      -- data key +      -- e.g. "author", "url" +      data_key    TEXT NOT NULL CHECK ( +                    LENGTH(data_key) > 0 AND +                    data_key NOT LIKE '% %' AND +                    data_key = LOWER(data_key) +                  ), + +      -- data value +      data_val    TEXT NOT NULL, + +      PRIMARY KEY (theme_id, type_id, data_key) +    ) + +  - | +    CREATE TABLE theme_files ( +      file_id     INTEGER PRIMARY KEY, + +      -- theme that this file is associated with +      theme_id    INTEGER NOT NULL +                  REFERENCES themes(theme_id), + +      -- size of file, in bytes +      file_size   INTEGER NOT NULL +                  CHECK (file_size > 0), + +      -- sha1 digest of file +      file_hash   TEXT NOT NULL CHECK ( +                    LENGTH(file_hash) > 1 AND +                    file_hash NOT LIKE '% %' +                  ), + +      -- path of file +      file_path   TEXT NOT NULL CHECK ( +                    LENGTH(file_path) > 1 AND +                    file_path NOT LIKE '/%' +                  ), + +      UNIQUE (theme_id, file_path) +    ) + +  - | +    CREATE TABLE theme_asset_types ( +      type_id     INTEGER PRIMARY KEY, +      name        TEXT UNIQUE NOT NULL +                  CHECK (LENGTH(name) > 0) +    ) + +  - | +    INSERT INTO theme_asset_types(type_id, name) VALUES +      (1, 'script'), +      (2, 'style') + +  - | +    CREATE TABLE theme_assets ( +      -- theme file +      file_id     INTEGER NOT NULL +                  REFERENCES theme_files(file_id), + +      -- asset type +      type_id     INTEGER NOT NULL +                  REFERENCES theme_asset_types(type_id), + +      -- load order +      sort_order  INTEGER NOT NULL, + +      PRIMARY KEY (file_id, type_id), +      UNIQUE (file_id, type_id) +    ) + +  - | +    CREATE TABLE sites ( +      site_id     INTEGER PRIMARY KEY, + +      -- make sure name does not begin with a dot or +      -- contain slashes +      name        TEXT UNIQUE NOT NULL CHECK ( +                    LENGTH(name) > 0 AND +                    name NOT LIKE '.%' AND +                    name NOT LIKE '%/%' +                  ), + +      -- date that site was created +      created_at  TIMESTAMP WITH TIME ZONE NOT NULL +                  DEFAULT CURRENT_TIMESTAMP, + +      -- theme for this site +      theme_id    INTEGER NOT NULL +                  REFERENCES themes(theme_id), + +      is_active   BOOLEAN NOT NULL DEFAULT false, + +      is_default  BOOLEAN NOT NULL DEFAULT false +    ) + +  - | +    INSERT INTO sites(site_id, name, theme_id, is_active, is_default) VALUES +      (1, 'default', 1, 1, 1) + +  - | +    CREATE TABLE site_domains ( +      site_id     INTEGER NOT NULL +                  REFERENCES sites(site_id), + +      domain      TEXT UNIQUE NOT NULL CHECK ( +                    LENGTH(domain) > 0 AND +                    domain = LOWER(domain) AND +                    domain NOT LIKE '% %' +                  ) +    ) + +  - |      CREATE TABLE states (        state_id    INTEGER PRIMARY KEY, @@ -154,34 +310,13 @@ init_sql:      )    - | -    CREATE TABLE layouts ( -      layout_id   INTEGER PRIMARY KEY, - -      -- internal layout name -      layout      TEXT UNIQUE NOT NULL CHECK ( -                    LENGTH(layout) > 0 AND -                    layout = LOWER(layout) -                  ), - -      -- user-visible layout name -      layout_name TEXT UNIQUE NOT NULL -                  CHECK (LENGTH(layout_name) > 0), - -      is_default  BOOLEAN NOT NULL -    ) - -  - | -    INSERT INTO layouts(layout_id, layout, layout_name, is_default) VALUES -      (1, 'blank', 'Blank', 0), -      (2, 'default', 'Default', 1) - -  - |      CREATE TABLE pages (        post_id     INTEGER PRIMARY KEY                    REFERENCES posts(post_id), -      layout_id   INTEGER NOT NULL -                  REFERENCES layouts(layout_id) +      -- nullable, NULL means site theme +      theme_id    INTEGER DEFAULT NULL +                  REFERENCES themes(theme_id)      )    - | @@ -246,9 +381,9 @@ test_posts:        SELECT post_id, name, slug, body FROM posts    - | -    INSERT INTO pages(post_id, layout_id) VALUES ( +    INSERT INTO pages(post_id, theme_id) VALUES (        1, -      (SELECT layout_id FROM layouts WHERE layout = 'default') +      (SELECT theme_id FROM themes WHERE theme_slug = 'default')      )    - | diff --git a/src/guff/apis.cr b/src/guff/apis.cr index dccc01c..bbedd3f 100644 --- a/src/guff/apis.cr +++ b/src/guff/apis.cr @@ -41,7 +41,8 @@ module Guff::APIs          name:             params["name"]?,          body:             params["body"]?, -        layout:           params["layout"]?, +        have_theme_id:    true, +        theme_id:         (params["theme_id"]? && params["theme_id"].size > 0) ? params["theme_id"].to_i32 : nil,        )        nil diff --git a/src/guff/model-set.cr b/src/guff/model-set.cr index afc7c5f..cf3c769 100644 --- a/src/guff/model-set.cr +++ b/src/guff/model-set.cr @@ -22,5 +22,6 @@ class Guff::ModelSet      role:     Models::RoleModel,      state:    Models::StateModel,      file:     Models::FileModel, +    theme:    Models::ThemeModel,    })  end diff --git a/src/guff/models/page.cr b/src/guff/models/page.cr index 9be3b3a..0ce8d5b 100644 --- a/src/guff/models/page.cr +++ b/src/guff/models/page.cr @@ -12,8 +12,8 @@ class Guff::Models::PageModel < Guff::Models::Model            ON (d.state_id = b.state_id)          JOIN users e            ON (e.user_id = b.created_by) -        JOIn layouts f -          ON (f.layout_id = c.layout_id) +        LEFT JOIN themes f +          ON (f.theme_id = c.theme_id)         WHERE a.site_id = ?           AND b.slug = ? @@ -30,9 +30,10 @@ class Guff::Models::PageModel < Guff::Models::Model               b.slug,               b.name,               b.body, -             f.layout,               e.user_id, -             e.name AS user_name +             e.name AS user_name, +             c.theme_id, +             a.theme_id AS site_theme_id          FROM sites a          JOIN posts b @@ -43,8 +44,6 @@ class Guff::Models::PageModel < Guff::Models::Model            ON (d.state_id = b.state_id)          JOIN users e            ON (e.user_id = b.created_by) -        JOIn layouts f -          ON (f.layout_id = c.layout_id)         WHERE a.site_id = ?           AND b.slug = ? @@ -59,13 +58,19 @@ class Guff::Models::PageModel < Guff::Models::Model      ",      add: " -      INSERT INTO pages(post_id, layout_id) -        VALUES (?, (SELECT layout_id FROM layouts WHERE layout = 'default')) +      INSERT INTO pages(post_id) +        VALUES (?)      ",      set: "        UPDATE pages -         SET layout_id = (SELECT layout_id FROM layouts WHERE layout = ?) +         SET theme_id = (SELECT theme_id FROM themes WHERE theme_id = ?) +       WHERE post_id = ? +    ", + +    set_default: " +      UPDATE pages +         SET theme_id = NULL         WHERE post_id = ?      ", @@ -79,15 +84,16 @@ class Guff::Models::PageModel < Guff::Models::Model               a.slug_lock,               a.name,               a.body, -             d.layout +             b.theme_id, +             d.theme_id AS site_theme_id          FROM posts a          JOIN pages b            ON (b.post_id = a.post_id)          JOIN states c            ON (c.state_id = a.state_id) -        JOIN layouts d -          ON (d.layout_id = b.layout_id) +        JOIN sites d +          ON (d.site_id = a.site_id)         WHERE a.post_id = ?      ", @@ -166,7 +172,8 @@ class Guff::Models::PageModel < Guff::Models::Model      name            : String? = nil,      body            : String? = nil, -    layout          : String? = nil, +    have_theme_id   : Bool    = false, +    theme_id        : Int32?  = nil,    )      db = @context.dbs.rw @@ -190,8 +197,12 @@ class Guff::Models::PageModel < Guff::Models::Model          body:             body,        ) -      if layout -        db.query(SQL[:set], [layout, post_id.to_s]) +      if have_theme_id +        if theme_id +          db.query(SQL[:set], [theme_id.to_s, post_id.to_s]) +        else +          db.query(SQL[:set_default], [post_id.to_s]) +        end        end      end    end diff --git a/src/guff/models/site.cr b/src/guff/models/site.cr index 8ab4162..99c09dd 100644 --- a/src/guff/models/site.cr +++ b/src/guff/models/site.cr @@ -38,6 +38,7 @@ class Guff::Models::SiteModel < Guff::Models::Model      get_sites: "        SELECT site_id,               name, +             theme_id,               is_active,               is_default diff --git a/src/guff/models/theme.cr b/src/guff/models/theme.cr new file mode 100644 index 0000000..a8682f7 --- /dev/null +++ b/src/guff/models/theme.cr @@ -0,0 +1,102 @@ +class Guff::Models::ThemeModel < Guff::Models::Model +  SQL = { +    all: " +      SELECT a.theme_id, +             a.theme_name, +             a.theme_version, +             a.theme_date, +             a.is_system, + +             (SELECT COUNT(*) +                FROM sites +               WHERE theme_id = a.theme_id) AS num_sites, +             (SELECT COUNT(*) +                FROM pages +               WHERE theme_id = a.theme_id) AS num_pages + +        FROM themes a + +       ORDER BY LOWER(a.theme_name), a.theme_date +    ", + +    get_data: " +      SELECT a.theme_id, +             a.data_key, +             a.data_val + +        FROM theme_data a +        JOIN theme_data_types b +          ON (b.type_id = a.type_id) + +       WHERE b.name = ? +         AND a.theme_id IN (%s) +    ", + +    get: " +      SELECT a.theme_id, +             a.theme_name, +             a.theme_version, +             a.theme_date, +             a.is_system, + +             (SELECT COUNT(*) +                FROM sites +               WHERE theme_id = a.theme_id) AS num_sites, +             (SELECT COUNT(*) +                FROM pages +               WHERE theme_id = a.theme_id) AS num_pages +        FROM themes a + +       ORDER BY LOWER(a.theme_name), a.theme_date +    ", +  } + +  def all(with_metadata : Bool = false) +    # get rows +    rows = [] of Hash(String, String) + +    @context.dbs.ro.all(SQL[:all]) do |row| +      rows << row.reduce({} of String => String) do |r, kv| +        r[kv[0]] = kv[1].to_s +        r +      end +    end + +    if with_metadata +      # get metadata lut +      lut = get_data("metadata", rows.map { |row| row["theme_id"].to_i }) + +      # build and return result +      rows.map { |row| +        lut[row["theme_id"]]? ? row.merge(lut[row["theme_id"]]) : row +      } +    else +      rows +    end +  end + +  def get(theme_id : Int32) +    id = theme_id.to_s +    r = @context.dbs.ro.row(SQL[:get], [id]).not_nil! +    lut = get_data("metadata", [theme_id]) +    lut[id]? ? r.merge(lut[id]) : r +  end + +  def get_data(type : String, theme_ids : Array(Int32)) +    r = {} of String => Hash(String, String) + +    if theme_ids.size > 0 +      @context.dbs.ro.all(SQL[:get_data] % [ +        (["?"] * theme_ids.size).join(",") +      ], [type].concat(theme_ids.map { |id| id.to_s })) do |row| +        r[row["theme_id"].to_s] ||= {} of String => String +        r[row["theme_id"].to_s][ +          "%s/%s" % [type, row["data_key"].to_s] +        ] = row["data_val"].to_s +      end +    end + +    # return results +    r +  end +end diff --git a/src/guff/views/admin-page.cr b/src/guff/views/admin-page.cr index 094b5f7..7761994 100644 --- a/src/guff/views/admin-page.cr +++ b/src/guff/views/admin-page.cr @@ -198,5 +198,22 @@ class Guff::Views::AdminPageView < Guff::Views::HTMLView      end    end +  private def theme_options +    [{ +      "id"    => "site-default", +      "name"  => "Site Default", +    }].concat(@context.models.theme.all.map { |row| +      { +        "id"    => row["theme_id"], +        "name"  => "%s (%s)" % %w{ +          name +          version +        }.map { |k| row["theme_#{k}"] }, +      } +    }).map { |row| +      TEMPLATES[:option] % %w{id name}.map { |k| row[k] } +    }.join("") +  end +    ECR.def_to_s("src/views/admin-page.ecr")  end diff --git a/src/views/admin-page.ecr b/src/views/admin-page.ecr index b441014..8581c01 100644 --- a/src/views/admin-page.ecr +++ b/src/views/admin-page.ecr @@ -1185,37 +1185,20 @@              <div class='row'>                <div class='col-md-6'> -                <label> -                  Layout +                <label for='page-edit-theme'> +                  Theme                  </label> -                <div -                  id='page-edit-layout' -                  class='btn-group btn-group-justified state-buttons' -                > -                  <a -                    href='#' -                    class='btn btn-default' -                    title='Use blank layout for this page.' -                    data-val='blank' -                  > -                    <i class='fa fa-file-o'></i> -                    Blank -                  </a> - -                  <a -                    href='#' -                    class='btn btn-primary' -                    title='Use default layout for this page.' -                    data-val='default' -                  > -                    <i class='fa fa-newspaper-o'></i> -                    Default -                  </a> -                </div><!-- btn-group --> +                <select +                  id='page-edit-theme' +                  class='form-control' +                  title='Choose theme for this page.' +                ><%= +                  theme_options +                %></select>                  <p class='help-block'> -                  Layout for this page. +                  Theme for this page.                  </p>                </div><!-- col-md-6 -->  | 
