Skip to content
34 changes: 20 additions & 14 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
{
"permissions": {
"allow": [
"Bash(grep:*)",
"Bash(chmod:*)",
"Bash(find:*)",
"Bash(ls:*)",
"Bash(mysql -u root:*)",

"Bash(bundle exec brakeman:*)",
"Bash(bundle exec rails routes:*)",
"Bash(bundle exec rails runner:*)",
"Bash(bundle exec rspec:*)",
"Bash(bundle exec rubocop:*)",

"Bash(RAILS_ENV=test bin/rails:*)",
"Bash(RAILS_ENV=test bin/rails db:migrate:*)",
"Bash(RAILS_ENV=test bundle exec rails runner:*)",
"Bash(RAILS_ENV=test bundle exec rspec:*)",

"Bash(git add:*)",
"Bash(git apply:*)",
"Bash(git checkout:*)",
"Bash(git commit:*)",
"Bash(git diff:*)",
"Bash(git fetch:*)",
"Bash(git log:*)",
"Bash(git pull:*)",
"Bash(git push:*)",
"Bash(git rebase:*)",
"Bash(git reset:*)",
"Bash(git stash:*)",
"Bash(bin/rails db:migrate:*)",
"Bash(bin/rails runner:*)",
"Bash(chmod:*)",
"Bash(mysql -u root:*)",
"Bash(git -C /Users/maebeale/programming/awbw branch --show-current)",
"Bash(bundle exec rails routes:*)",
"Bash(bundle exec rubocop:*)",
"Bash(git -C /Users/maebeale/programming/awbw diff --name-only main...HEAD)",
"Bash(git log:*)",

"Bash(git fetch:*)",
"Bash(git rebase:*)",
"Bash(grep:*)"
"Bash(git status:*)",

"Bash(bin/rails generate:*)",
"Bash(bin/rails db:migrate:*)",
"Bash(bin/rails db:schema:dump:*)",
"Bash(RAILS_ENV=test bin/rails:*)"
"Bash(bin/rails db:seed:*)",
"Bash(bin/rails runner:*)",
"Bash(bin/rails test:*)",

"Bash(git -C /Users/maebeale/programming/awbw branch --show-current)",
"Bash(git -C /Users/maebeale/programming/awbw diff --name-only main...HEAD)",
]
}
}
2 changes: 1 addition & 1 deletion app/controllers/community_news_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def set_form_variables
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.select { |type, _| type.nil? || (type.published? && !type.story_specific?) }
.sort_by { |type, _| type&.name.to_s.downcase }
@sectors = Sector.published.order(:name)
@community_news.build_primary_asset if @community_news.primary_asset.blank?
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def set_form_variables
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.select { |type, _| type.nil? || (type.published? && !type.story_specific?) }
.sort_by { |type, _| type&.name.to_s.downcase }
@sectors = Sector.published.order(:name)
end
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/resources_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def set_form_variables
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.select { |type, _| type.nil? || (type.published? && !type.story_specific?) }
.sort_by { |type, _| type&.name.to_s.downcase }
@sectors = Sector.published.order(:name)
end
Expand Down
21 changes: 17 additions & 4 deletions app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def edit
end

def create
@story = Story.new(story_params)
@story = Story.new(story_params.except(:category_ids, :sector_ids))
authorize! @story

success = false
Expand Down Expand Up @@ -144,8 +144,11 @@ def update
success = false

Story.transaction do
if @story.update(story_params.except(:images))
if @story.update(story_params.except(:images, :category_ids, :sector_ids))
assign_associations(@story)
if params[:promote_idea_assets] == "true"
@story.attach_assets_from_idea!
end
success = true
end
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
Expand Down Expand Up @@ -188,8 +191,17 @@ def set_form_variables
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.sort_by { |type, _| type&.name.to_s.downcase }
.sort_by { |type, _| [ type&.story_specific? ? 0 : 1, type&.name.to_s.downcase ] }
@sectors = Sector.published.order(:name)
submitted_sector_ids = Array(params.dig(:story, :sector_ids)).reject(&:blank?)
submitted_category_ids = Array(params.dig(:story, :category_ids)).reject(&:blank?)
if submitted_sector_ids.any? || submitted_category_ids.any?
@preselected_sector_ids = submitted_sector_ids.map(&:to_i)
@preselected_category_ids = submitted_category_ids.map(&:to_i)
elsif @story_idea
@preselected_sector_ids = @story_idea.sector_ids
@preselected_category_ids = @story_idea.category_ids
end
@story.build_primary_asset if @story.primary_asset.blank?
@story.gallery_assets.build
end
Expand Down Expand Up @@ -219,12 +231,13 @@ def story_params
category_ids: [],
sector_ids: [],
primary_asset_attributes: [ :id, :file, :_destroy ],
gallery_assets_attributes: [ :id, :file, :_destroy ]
gallery_assets_attributes: [ :id, :file, :_destroy ],
)
end

def set_story_attributes_from(idea)
{
story_idea_id: idea.id,
rhino_body: idea.body,
organization_id: idea.organization.id,
workshop_id: idea.workshop_id,
Expand Down
81 changes: 69 additions & 12 deletions app/controllers/story_ideas_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,30 @@ def edit
end

def create
@story_idea = StoryIdea.new(story_idea_params)
@story_idea = StoryIdea.new(story_idea_params.except(:category_ids, :sector_ids))
@story_idea.created_by = current_user
@story_idea.updated_by = current_user
authorize! @story_idea

if @story_idea.save
NotificationServices::CreateNotification.call(
noticeable: @story_idea,
kind: :idea_submitted_fyi,
recipient_role: :admin,
recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"),
notification_type: 0)
success = false

StoryIdea.transaction do
if @story_idea.save
assign_associations(@story_idea)
NotificationServices::CreateNotification.call(
noticeable: @story_idea,
kind: :idea_submitted_fyi,
recipient_role: :admin,
recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"),
notification_type: 0)
success = true
end
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
Rails.logger.error "StoryIdea create failed: #{e.class} - #{e.message}"
raise ActiveRecord::Rollback
end

if success
flash[:notice] = "StoryIdea was successfully created."
if allowed_to?(:index?, StoryIdea)
redirect_to story_ideas_path
Expand All @@ -59,7 +70,19 @@ def update
@story_idea.updated_by = current_user
authorize! @story_idea

if @story_idea.update(story_idea_params.except(:images))
success = false

StoryIdea.transaction do
if @story_idea.update(story_idea_params.except(:images, :category_ids, :sector_ids))
assign_associations(@story_idea)
success = true
end
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
Rails.logger.error "StoryIdea update failed: #{e.class} - #{e.message}"
raise ActiveRecord::Rollback
end

if success
flash[:notice] = "StoryIdea was successfully updated."
if allowed_to?(:index?, StoryIdea)
redirect_to story_ideas_path, status: :see_other
Expand Down Expand Up @@ -88,13 +111,44 @@ def set_form_variables

@workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title)

@users = authorized_scope(User.has_access.includes(:person))
@users = @users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id
@users = @users.includes(:person).distinct.order("people.first_name, people.last_name")
users = authorized_scope(User.has_access.includes(:person))
users = users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id
@users = users.distinct.order("people.first_name, people.last_name")

@story_population_type = CategoryType.find_by(name: "StoryPopulation")
@story_population_categories = @story_population_type&.categories&.published&.order(:name) || []
@sectors = Sector.published.order(:name)
submitted_sector_ids = Array(params.dig(:story_idea, :sector_ids)).reject(&:blank?)
submitted_category_ids = Array(params.dig(:story_idea, :category_ids)).reject(&:blank?)
if submitted_sector_ids.any? || submitted_category_ids.any?
@preselected_sector_ids = submitted_sector_ids.map(&:to_i)
@preselected_category_ids = submitted_category_ids.map(&:to_i)
end

if @story_idea.persisted?
@categories_grouped =
Category
.includes(:category_type)
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.sort_by { |type, _| [ type&.story_specific? ? 0 : 1, type&.name.to_s.downcase ] }
end
>>>>>>> ac3201a15 (Story and StoryIdea display and promotion process wip)
@story_idea.build_primary_asset if @story_idea.primary_asset.blank?
@story_idea.gallery_assets.build
end

def assign_associations(story_idea)
selected_category_ids = Array(params[:story_idea][:category_ids]).reject(&:blank?).map(&:to_i)
story_idea.categories = Category.where(id: selected_category_ids)

selected_sector_ids = Array(params[:story_idea][:sector_ids]).reject(&:blank?).map(&:to_i)
story_idea.sectors = Sector.where(id: selected_sector_ids)
story_idea.save!
end

private

def set_story_idea
Expand All @@ -106,6 +160,9 @@ def story_idea_params
:title, :body, :youtube_url,
:permission_given, :publish_preferences, :promoted_to_story,
:windows_type_id, :organization_id, :workshop_id, :external_workshop_title,
:created_by_id, :updated_by_id,
category_ids: [],
sector_ids: [],
primary_asset_attributes: [ :id, :file, :_destroy ],
gallery_assets_attributes: [ :id, :file, :_destroy ]
)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/workshop_ideas_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def set_form_variables
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.select { |type, _| type.nil? || (type.published? && !type.story_specific?) }
.sort_by { |type, _| type&.name.to_s.downcase }
@workshop_idea.build_primary_asset if @workshop_idea.primary_asset.blank?
@workshop_idea.gallery_assets.build
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/workshops_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def set_form_variables
.published
.order(:position, :name)
.group_by(&:category_type)
.select { |type, _| type.nil? || type.published? }
.select { |type, _| type.nil? || (type.published? && !type.story_specific?) }
.sort_by { |type, _| type&.name.to_s.downcase }

@sectors = Sector.published.order(:name)
Expand Down
5 changes: 3 additions & 2 deletions app/helpers/person_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module PersonHelper
def person_profile_button(person, truncate_at: nil, subtitle: nil)
def person_profile_button(person, truncate_at: nil, subtitle: nil, display_name: nil)
bg = DomainTheme.bg_class_for(:people, intensity: 100)
hover_bg = DomainTheme.bg_class_for(:people, intensity: 100, hover: true)
text = DomainTheme.text_class_for(:people)
Expand Down Expand Up @@ -28,7 +28,8 @@ def person_profile_button(person, truncate_at: nil, subtitle: nil)
border border-sky-300 shadow-sm flex-shrink-0")
end

display_name = truncate_at ? truncate(person.name.to_s, length: truncate_at) : person.name.to_s
display_name = display_name || person.name.to_s
display_name = truncate(display_name, length: truncate_at) if truncate_at

name = content_tag(
:span,
Expand Down
10 changes: 10 additions & 0 deletions app/helpers/title_display_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ def title_with_badges(record, font_size: "text-lg", record_title: nil,
)
end

# --- Promoted from story idea badge ---
if record.respond_to?(:story_idea) && record.story_idea.present?
fragments << content_tag(
:span,
content_tag(:i, "", class: "fa-solid fa-arrow-up-from-bracket mr-1") + " Promoted",
class: "inline-flex items-center px-2 py-0.5 rounded-full
text-sm font-medium bg-green-100 text-green-800 whitespace-nowrap"
)
end

title_content = record_title || record.title.to_s

if display_windows_type && record.respond_to?(:windows_type) && record.windows_type.present?
Expand Down
1 change: 1 addition & 0 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Category < ApplicationRecord
# Scopes
# See NameFilterable, Publishable
scope :age_ranges, -> { joins(:category_type).where(category_types: { name: "AgeRange" }) }
scope :story_categories, -> { joins(:category_type).where(category_types: { name: "StoryCategory" }) }
scope :ordered_by_position_and_name, -> { reorder(position: :asc, name: :asc) }

# Validations
Expand Down
6 changes: 6 additions & 0 deletions app/models/category_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ class CategoryType < ApplicationRecord

# Scopes
# See Publishable
scope :general, -> { where(story_specific: false) }
scope :story_specific, -> { where(story_specific: true) }

def display_label
display_text.presence || name.titleize
end
end
2 changes: 2 additions & 0 deletions app/models/sector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class Sector < ApplicationRecord
"Other"
]

STORY_DISPLAY_TEXT = "Which sectors apply?"

has_many :sectorable_items, dependent: :destroy
has_many :workshops, through: :sectorable_items,
source: :sectorable, source_type: "Workshop"
Expand Down
1 change: 1 addition & 0 deletions app/models/story.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def organization_description

def attach_assets_from_idea!
return unless story_idea
assets.destroy_all
story_idea.assets.find_each do |asset|
new_asset = assets.build(type: asset.type)
new_asset.file.attach(asset.file.blob)
Expand Down
5 changes: 5 additions & 0 deletions app/models/story_idea.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class StoryIdea < ApplicationRecord
belongs_to :windows_type
belongs_to :workshop, optional: true
has_many :bookmarks, as: :bookmarkable, dependent: :destroy
has_many :categorizable_items, dependent: :destroy, inverse_of: :categorizable, as: :categorizable
has_many :sectorable_items, dependent: :destroy, inverse_of: :sectorable, as: :sectorable
has_many :notifications, as: :noticeable, dependent: :destroy
has_many :stories
# Asset associations
Expand All @@ -19,6 +21,9 @@ class StoryIdea < ApplicationRecord
has_many :gallery_assets, -> { where(type: "GalleryAsset") },
as: :owner, class_name: "GalleryAsset", dependent: :destroy
has_many :assets, as: :owner, dependent: :destroy
# has_many through
has_many :categories, through: :categorizable_items
has_many :sectors, through: :sectorable_items

# Validations
validates :created_by_id, presence: true
Expand Down
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def full_name
end
end

def full_name_with_email
"#{full_name} (#{email})"
end

def devise_email_name
person&.first_name.presence || first_name.presence || email
end
Expand Down
Loading