diff --git a/app/api/v1/entities/content_item.rb b/app/api/v1/entities/content_item.rb
index 5234f34f4..b3bb0bd93 100644
--- a/app/api/v1/entities/content_item.rb
+++ b/app/api/v1/entities/content_item.rb
@@ -3,11 +3,6 @@ module Entities
class ContentItem < Grape::Entity
expose :id, documentation: {type: "Integer", desc: "Content Item ID", required: true}
expose :publish_state, documentation: {type: "String", desc: "Publish state", required: true}
- expose :published_at, documentation: {type: "dateTime", desc: "Date published", required: true}
- expose :expired_at, documentation: {type: "dateTime", desc: "Date to expire", required: true}
- expose :author, documentation: {type: "dateTime", desc: "Date published", required: true} do |content_item|
- content_item.author.fullname
- end
expose :creator, documentation: {type: "dateTime", desc: "Date published", required: true} do |content_item|
content_item.creator.fullname
end
diff --git a/app/api/v1/resources/content_items.rb b/app/api/v1/resources/content_items.rb
index 15b52fcb3..5c5f17f51 100644
--- a/app/api/v1/resources/content_items.rb
+++ b/app/api/v1/resources/content_items.rb
@@ -1,13 +1,19 @@
module V1
module Resources
class ContentItems < Grape::API
+ helpers ::V1::Helpers::SharedParamsHelper
+ helpers ::V1::Helpers::ParamsHelper
+
resource :content_items do
+ include Grape::Kaminari
+ paginate per_page: 25
+
desc "Create a content item", { entity: ::V1::Entities::ContentItem, params: ::V1::Entities::ContentItem.documentation, nickname: "createContentItem" }
params do
requires :content_type_id, type: Integer, desc: "content type of content item"
end
post do
- require_scope! 'create:content_items'
+ require_scope! 'modify:content_items'
authorize! :create, ::ContentItem
@content_item = ::ContentItem.new(params.merge(author_id: current_user.id, creator_id: current_user.id))
@@ -27,6 +33,37 @@ class ContentItems < Grape::API
present @content_items, with: ::V1::Entities::ContentItem, field_items: true
end
+
+ desc 'Show published content items', { entity: ::V1::Entities::ContentItem, nickname: "contentItemsFeed" }
+ params do
+ use :pagination
+ requires :content_type_name, type: String, desc: 'ContentType of ContentItem'
+ end
+ get :feed do
+ require_scope! 'view:content_items'
+ authorize! :view, ::ContentItem
+
+ last_updated_at = ContentItem.last_updated_at
+ params_hash = Digest::MD5.hexdigest(declared(params).to_s)
+ cache_key = "feed-#{last_updated_at}-#{current_tenant.id}-#{params_hash}"
+
+ content_items = ::Rails.cache.fetch(cache_key, expires_in: 30.minutes, race_condition_ttl: 10) do
+ content_items = ::GetContentItems.call(params: declared(clean_params(params), include_missing: false), tenant: current_tenant, published: true).content_items
+ paginated_content_items = paginate(content_items).records.to_a
+ {records: paginated_content_items, headers: header}
+ end
+
+ header.merge!(content_items[:headers])
+ ::V1::Entities::ContentItem.represent content_items[:records], field_items: true
+ end
+
+ desc 'Show a published content item', { entity: ::V1::Entities::ContentItem, nickname: "showFeedContentItem" }
+ get 'feed/*id' do
+ @content_item = ::GetContentItem.call(id: params[:id], published: true, tenant: current_tenant.id).content_item
+ not_found! unless @content_item
+ authorize! :view, @content_item
+ present @content_item, with: ::V1::Entities::ContentItem, field_items: true
+ end
end
end
end
diff --git a/app/assets/javascripts/legacy/controllers/webpages/edit.js b/app/assets/javascripts/legacy/controllers/webpages/edit.js
index 2a5575adf..367d907ae 100644
--- a/app/assets/javascripts/legacy/controllers/webpages/edit.js
+++ b/app/assets/javascripts/legacy/controllers/webpages/edit.js
@@ -72,4 +72,16 @@ angular.module('cortex.controllers.webpages.edit', [
page: page.page
});
}
+
+ $scope.appendEditingParamsToUrl = function(url) {
+ var urlHasParams = _.includes(url, '?');
+
+ if (urlHasParams) {
+ url = url + '&editing_mode=1&disable_redirects=1';
+ } else {
+ url = url + '?editing_mode=1&disable_redirects=1';
+ }
+
+ return url;
+ }
});
diff --git a/app/assets/legacy_templates/webpages/edit.html b/app/assets/legacy_templates/webpages/edit.html
index 97d86b6d8..7922ace94 100644
--- a/app/assets/legacy_templates/webpages/edit.html
+++ b/app/assets/legacy_templates/webpages/edit.html
@@ -129,6 +129,6 @@
-
+
diff --git a/app/interactors/get_content_item.rb b/app/interactors/get_content_item.rb
new file mode 100644
index 000000000..6ac89dc1c
--- /dev/null
+++ b/app/interactors/get_content_item.rb
@@ -0,0 +1,12 @@
+class GetContentItem
+ include Interactor
+
+ def call
+ content_item = ::ContentItem
+ #content_item = content_item.find_by_tenant_id(context.tenant) if context.tenant
+ #content_item = content_item.published if context.published
+ #content_item = content_item.find_by_id_or_slug(context.id)
+ content_item = content_item.find_by_id(context.id)
+ context.content_item = content_item
+ end
+end
diff --git a/app/interactors/get_content_items.rb b/app/interactors/get_content_items.rb
new file mode 100644
index 000000000..886cdea32
--- /dev/null
+++ b/app/interactors/get_content_items.rb
@@ -0,0 +1,8 @@
+class GetContentItems
+ include Interactor
+
+ def call
+ content_items = ContentType.find_by_name(context.params.content_type_name.titleize).content_items.order(created_at: :desc)
+ context.content_items = content_items
+ end
+end
diff --git a/app/models/content_item.rb b/app/models/content_item.rb
index 3a2117360..4e2a88edb 100644
--- a/app/models/content_item.rb
+++ b/app/models/content_item.rb
@@ -3,18 +3,7 @@ class ContentItem < ApplicationRecord
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
- state_machine do
- state :draft
- state :scheduled
-
- event :schedule do
- transitions :to => :scheduled, :from => [:draft]
- end
-
- event :draft do
- transitions :to => :draft, :from => [:scheduled]
- end
- end
+ scope :last_updated_at, -> { order(updated_at: :desc).select('updated_at').first.updated_at }
acts_as_paranoid
@@ -32,6 +21,19 @@ class ContentItem < ApplicationRecord
after_save :index
after_save :update_tag_lists
+ state_machine do
+ state :draft
+ state :scheduled
+
+ event :schedule do
+ transitions :to => :scheduled, :from => [:draft]
+ end
+
+ event :draft do
+ transitions :to => :draft, :from => [:scheduled]
+ end
+ end
+
def self.taggable_fields
Field.select { |field| field.field_type_instance.is_a?(TagFieldType) }.map { |field_item| field_item.name.parameterize('_') }
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index c9abd248b..d3d7aabd9 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -39,7 +39,8 @@
optional_scopes 'view:users', 'modify:users', 'view:tenants', 'modify:tenants', 'view:posts',
'modify:posts', 'view:media', 'modify:media', 'view:applications', 'modify:applications',
'view:bulk_jobs', 'modify:bulk_jobs', 'view:documents', 'modify:documents',
- 'view:snippets', 'modify:snippets', 'view:webpages', 'modify:webpages', 'view:content_types'
+ 'view:snippets', 'modify:snippets', 'view:webpages', 'modify:webpages', 'view:content_types',
+ 'modify:content_types', 'view:content_items', 'modify:content_items'
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
diff --git a/db/schema.rb b/db/schema.rb
index e0da3f489..a7efa7be7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -472,8 +472,8 @@
t.string "dynamic_yield_sku"
t.string "dynamic_yield_category"
t.jsonb "tables_widget"
- t.jsonb "accordion_group_widget"
t.jsonb "charts_widget"
+ t.jsonb "accordion_group_widget"
t.index ["user_id"], name: "index_webpages_on_user_id", using: :btree
end
diff --git a/db/seeds.rb b/db/seeds.rb
index e5d17cd1c..623bf11e5 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -12,9 +12,7 @@
job_phase: initial_post_seed.job_phase,
display: initial_post_seed.display,
copyright_owner: initial_post_seed.copyright_owner,
- categories: [Category.first],
- primary_category: Category.first,
- author: User.first)
+ author: Author.first)
existing_tenant = Tenant.find_by_name(tenant_seed.name)
diff --git a/lib/tasks/employer/integration.rake b/lib/tasks/employer/integration.rake
new file mode 100644
index 000000000..b2efaaa6c
--- /dev/null
+++ b/lib/tasks/employer/integration.rake
@@ -0,0 +1,261 @@
+Bundler.require(:default, Rails.env)
+
+namespace :employer do
+ namespace :integration do
+ desc 'Seed Employer Integration ContentType and Fields'
+ task seed: :environment do
+ def category_tree
+ tree = Tree.new
+ tree.add_node({ name: "Category 1" })
+ tree.add_node({ name: "Category 2" })
+ tree.add_node({ name: "Category 3" })
+ tree
+ end
+
+ puts 'Creating Employer Integration ContentType...'
+ integration = ContentType.new({
+ name: "Employer Integration",
+ description: "3rd Party CareerBuilder Integrations",
+ icon: "device hub",
+ creator_id: 1,
+ contract_id: 1,
+ publishable: true
+ })
+ integration.save!
+
+ puts "Creating Fields..."
+ integration.fields.new(name: 'Body', field_type: 'text_field_type', metadata: {parse_widgets: true}, validations: { presence: true })
+ integration.fields.new(name: 'Title', field_type: 'text_field_type', validations: {presence: true})
+ integration.fields.new(name: 'Description', field_type: 'text_field_type', validations: {presence: true})
+ integration.fields.new(name: 'Slug', field_type: 'text_field_type', validations: {presence: true, uniqueness: true})
+ integration.fields.new(name: 'Tags', field_type: 'tag_field_type')
+ integration.fields.new(name: 'Publish Date', field_type: 'date_time_field_type', metadata: {state: 'Published'})
+ integration.fields.new(name: 'Expiration Date', field_type: 'date_time_field_type', metadata: {state: 'Expired'})
+ integration.fields.new(name: 'SEO Title', field_type: 'text_field_type', validations: {presence: true, uniqueness: true })
+ integration.fields.new(name: 'SEO Description', field_type: 'text_field_type', validations: {presence: true})
+ integration.fields.new(name: 'SEO Keywords', field_type: 'tag_field_type')
+ integration.fields.new(name: 'No Index', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'No Follow', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'No Snippet', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'No ODP', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'No Archive', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'No Image Index', field_type: 'boolean_field_type')
+ integration.fields.new(name: 'Categories', field_type: 'tree_field_type', metadata: {allowed_values: category_tree})
+ integration.fields.new(name: 'Featured Image', field_type: 'content_item_field_type',
+ metadata: {
+ field_name: 'Asset'
+ })
+
+ puts "Saving Employer Integration..."
+ integration.save!
+
+ puts "Creating Wizard Decorators..."
+ wizard_hash = {
+ "steps": [
+ {
+ "name": "Write",
+ "columns": [
+ {
+ "grid_width": 12,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('Title').id
+ },
+ {
+ "id": integration.fields.find_by_name('Body').id,
+ "render_method": "wysiwyg",
+ "input": {
+ "display": {
+ "styles": {
+ "height": "500px"
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+
+ {
+ "name": "Details",
+ "columns": [
+ {
+ "grid_width": 6,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('Description').id,
+ "tooltip": 'This is a short description and will be used as the preview text for an employer before they click into the integration.'
+ },
+ {
+ "id": integration.fields.find_by_name('Publish Date').id
+ },
+ {
+ "id": integration.fields.find_by_name('Expiration Date').id
+ }
+ ]
+ },
+ {
+ "grid_width": 6,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('Tags').id
+ },
+ {
+ "id": integration.fields.find_by_name('Slug').id,
+ "tooltip": "This is your integrations's URL. Between each word, place a hyphen. Best if between 35-50 characters and don't include years/dates."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Categorize",
+ "columns": [
+ {
+ "grid_width": 4,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('Categories').id,
+ "render_method": "checkboxes"
+ }
+ ]
+ },
+ {
+ "grid_width": 8,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('Featured Image').id,
+ "render_method": "popup"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Search",
+ "columns": [
+ {
+ "grid_width": 6,
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('SEO Title').id,
+ "tooltip": 'Please use <70 characters for your SEO title for optimal appearance in search results.'
+ },
+ {
+ "id": integration.fields.find_by_name('SEO Description').id,
+ "tooltip": 'The description should optimally be between 150-160 characters and keyword rich.'
+ },
+ {
+ "id": integration.fields.find_by_name('SEO Keywords').id,
+ "tooltip": 'Utilize the recommended keywords as tags to boost your SEO performance.'
+ }
+ ]
+ },
+ {
+ "grid_width": 6,
+ "description": "Select these if you don't want your integration to be indexed by search engines like Google",
+ "elements": [
+ {
+ "id": integration.fields.find_by_name('No Index').id
+ },
+ {
+ "id": integration.fields.find_by_name('No Follow').id
+ },
+ {
+ "id": integration.fields.find_by_name('No Snippet').id
+ },
+ {
+ "id": integration.fields.find_by_name('No ODP').id
+ },
+ {
+ "id": integration.fields.find_by_name('No Archive').id
+ },
+ {
+ "id": integration.fields.find_by_name('No Image Index').id
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+
+ wizard_decorator = Decorator.new(name: "Wizard", data: wizard_hash)
+ wizard_decorator.save!
+
+ ContentableDecorator.create!({
+ decorator_id: wizard_decorator.id,
+ contentable_id: integration.id,
+ contentable_type: 'ContentType'
+ })
+
+ puts "Creating Index Decorators..."
+ index_hash = {
+ "columns":
+ [
+ {
+ "name": "Title",
+ "grid_width": 3,
+ "cells": [{
+ "field": {
+ "id": integration.fields.find_by_name('Title').id
+ }
+ }]
+ },
+ {
+ "name": "Integration Details",
+ "cells": [
+ {
+ "field": {
+ "id": integration.fields.find_by_name('Description').id
+ },
+ "display": {
+ "classes": [
+ "bold",
+ "upcase"
+ ]
+ }
+ },
+ {
+ "field": {
+ "id": integration.fields.find_by_name('Slug').id
+ }
+ },
+ {
+ "field": {
+ "method": "publish_state"
+ }
+ }
+ ]
+ },
+ {
+ "name": "Tags",
+ "cells": [
+ {
+ "field": {
+ "id": integration.fields.find_by_name('Tags').id
+ },
+ "display": {
+ "classes": [
+ "tag",
+ "rounded"
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+
+ index_decorator = Decorator.new(name: "Index", data: index_hash)
+ index_decorator.save!
+
+ ContentableDecorator.create!({
+ decorator_id: index_decorator.id,
+ contentable_id: integration.id,
+ contentable_type: 'ContentType'
+ })
+ end
+ end
+end