Skip to content

Commit 6ce8d38

Browse files
committed
Support for multiple mailing lists
1 parent 85701dc commit 6ce8d38

41 files changed

Lines changed: 916 additions & 141 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ENGINE ?= $(shell command -v podman >/dev/null 2>&1 && echo podman || echo docker)
22
COMPOSE ?= $(ENGINE) compose --env-file .env.development -f compose.dev.yml
33

4-
.PHONY: dev dev-detach down shell console test imap logs db-migrate db-reset db-import stats psql sim-email-once sim-email-stream
4+
.PHONY: dev dev-detach down shell console test imap logs db-migrate db-reset db-import mbox-import stats psql sim-email-once sim-email-stream
55

66
dev: ## Start dev stack (foreground)
77
$(COMPOSE) up --build
@@ -44,12 +44,26 @@ db-reset: ## Drop and setup (create/migrate/seed) - stops web if running, restar
4444

4545
db-import: ## Drop dev DB and import a public dump (env: DUMP=/path/to/public-YYYY-MM.sql.gz)
4646
@if [ -z "$(DUMP)" ]; then echo "Set DUMP=/path/to/public-YYYY-MM.sql.gz"; exit 1; fi
47+
@if [ -z "$(PDUMP)" ]; then echo "Set PDUMP=/path/to/private-schema-YYYY-MM.sql.gz"; exit 1; fi
4748
$(COMPOSE) exec -T db bash -lc 'psql -U $${POSTGRES_USER:-hackorum} -d postgres -c "DROP DATABASE IF EXISTS $${POSTGRES_DB:-hackorum_development};" -c "CREATE DATABASE $${POSTGRES_DB:-hackorum_development};"'
4849
@if echo "$(DUMP)" | grep -qE '\.gz$$'; then \
4950
gzip -cd "$(DUMP)" | $(COMPOSE) exec -T db bash -lc 'psql -U $${POSTGRES_USER:-hackorum} -d $${POSTGRES_DB:-hackorum_development}'; \
5051
else \
5152
cat "$(DUMP)" | $(COMPOSE) exec -T db bash -lc 'psql -U $${POSTGRES_USER:-hackorum} -d $${POSTGRES_DB:-hackorum_development}'; \
5253
fi
54+
@if echo "$(PDUMP)" | grep -qE '\.gz$$'; then \
55+
gzip -cd "$(PDUMP)" | $(COMPOSE) exec -T db bash -lc 'psql -U $${POSTGRES_USER:-hackorum} -d $${POSTGRES_DB:-hackorum_development}'; \
56+
else \
57+
cat "$(PDUMP)" | $(COMPOSE) exec -T db bash -lc 'psql -U $${POSTGRES_USER:-hackorum} -d $${POSTGRES_DB:-hackorum_development}'; \
58+
fi
59+
60+
mbox-import: ## Import mbox files (env: LIST=id MBOX_DIR=/path MBOX_FILES="*.mbox" ARGS="--update-body")
61+
@if [ -z "$(LIST)" ]; then echo "Set LIST=<mailing-list-identifier>"; exit 1; fi
62+
@if [ -z "$(MBOX_DIR)" ]; then echo "Set MBOX_DIR=/path/to/mbox/directory"; exit 1; fi
63+
@if [ -z "$(MBOX_FILES)" ]; then echo "Set MBOX_FILES='*.mbox' or specific filenames"; exit 1; fi
64+
$(COMPOSE) run --rm \
65+
-v $(MBOX_DIR):/import:ro,z \
66+
web bash -c 'ruby script/mbox_import.rb --list $(LIST) $(ARGS) $(addprefix /import/,$(MBOX_FILES))'
5367

5468
stats: ## Rebuild stats (env: GRANULARITY=all|daily|weekly|monthly)
5569
$(COMPOSE) exec web bundle exec ruby script/build_stats.rb $${GRANULARITY:-all}

app/assets/stylesheets/components/topics.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,3 +1020,26 @@ a.topic-icon {
10201020
margin: var(--spacing-8);
10211021
text-align: center;
10221022
}
1023+
1024+
.mailing-list-badges {
1025+
display: inline-flex;
1026+
gap: 4px;
1027+
margin-right: 6px;
1028+
vertical-align: middle;
1029+
}
1030+
1031+
.mailing-list-badge {
1032+
display: inline-block;
1033+
padding: 1px 6px;
1034+
font-size: 0.7rem;
1035+
border-radius: 3px;
1036+
background: var(--color-primary-3);
1037+
color: var(--color-text-muted);
1038+
white-space: nowrap;
1039+
line-height: 1.4;
1040+
}
1041+
1042+
.message-lists {
1043+
display: inline-flex;
1044+
gap: 4px;
1045+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::MailingListsController < Admin::BaseController
4+
before_action :set_mailing_list, only: [:edit, :update]
5+
6+
def active_admin_section
7+
:mailing_lists
8+
end
9+
10+
def index
11+
@mailing_lists = MailingList
12+
.select(
13+
"mailing_lists.*",
14+
"(SELECT COUNT(*) FROM message_mailing_lists WHERE message_mailing_lists.mailing_list_id = mailing_lists.id) AS messages_count",
15+
"(SELECT COUNT(*) FROM topic_mailing_lists WHERE topic_mailing_lists.mailing_list_id = mailing_lists.id) AS topics_count"
16+
)
17+
.order(:display_name)
18+
19+
@topic_list_distribution = TopicMailingList.connection.select_rows(<<~SQL).to_h { |c, t| [c.to_i, t.to_i] }
20+
SELECT lists_count, COUNT(*) AS topic_count
21+
FROM (SELECT COUNT(*) AS lists_count FROM topic_mailing_lists GROUP BY topic_id) counts
22+
GROUP BY lists_count ORDER BY lists_count
23+
SQL
24+
end
25+
26+
def new
27+
@mailing_list = MailingList.new
28+
end
29+
30+
def create
31+
@mailing_list = MailingList.new(mailing_list_params)
32+
if @mailing_list.save
33+
redirect_to admin_mailing_lists_path, notice: "Mailing list created"
34+
else
35+
render :new, status: :unprocessable_entity
36+
end
37+
end
38+
39+
def edit
40+
end
41+
42+
def update
43+
if @mailing_list.update(mailing_list_params)
44+
redirect_to admin_mailing_lists_path, notice: "Mailing list updated"
45+
else
46+
render :edit, status: :unprocessable_entity
47+
end
48+
end
49+
50+
private
51+
52+
def set_mailing_list
53+
@mailing_list = MailingList.find(params[:id])
54+
end
55+
56+
def mailing_list_params
57+
params.require(:mailing_list).permit(:identifier, :display_name, :email, :description)
58+
end
59+
end

app/controllers/topics_controller.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ def index
99
apply_cursor_pagination(base_query)
1010
preload_topic_participants
1111
preload_commitfest_summaries
12+
preload_topic_mailing_lists
1213
@new_topics_count = 0
1314
@page_cache_key = topics_page_cache_key
1415

1516
load_visible_tags if user_signed_in?
1617
load_saved_searches
18+
@all_mailing_lists = MailingList.order(:display_name)
1719

1820
respond_to do |format|
1921
format.html
@@ -70,6 +72,17 @@ def show
7072
build_participants_sidebar_data(messages_scope)
7173
build_thread_outline(@messages)
7274
load_commitfest_sidebar
75+
76+
@topic_mailing_lists = @topic.mailing_lists.to_a
77+
@topic_is_multi_list = @topic_mailing_lists.size > 1
78+
79+
if @topic_is_multi_list
80+
msg_ids = @messages.map(&:id)
81+
mml_records = MessageMailingList.where(message_id: msg_ids).includes(:mailing_list)
82+
@message_mailing_lists_map = mml_records.group_by(&:message_id)
83+
.transform_values { |mmls| mmls.map(&:mailing_list) }
84+
end
85+
7386
if user_signed_in?
7487
load_notes
7588
load_star_state
@@ -301,9 +314,11 @@ def search
301314

302315
preload_topic_participants
303316
preload_commitfest_summaries
317+
preload_topic_mailing_lists
304318
preload_participation_flags if user_signed_in?
305319
load_visible_tags if user_signed_in?
306320
load_saved_searches
321+
@all_mailing_lists = MailingList.order(:display_name)
307322

308323
respond_to do |format|
309324
format.html
@@ -365,6 +380,7 @@ def user_state_frame
365380
preload_commitfest_summaries
366381
preload_star_counts
367382
preload_topic_participants
383+
preload_topic_mailing_lists
368384

369385
respond_to do |format|
370386
format.turbo_stream
@@ -670,6 +686,17 @@ def load_saved_searches
670686
end
671687
end
672688

689+
def preload_topic_mailing_lists
690+
topic_ids = @topics.map(&:id)
691+
return if topic_ids.empty?
692+
693+
@topic_mailing_lists_map = TopicMailingList
694+
.where(topic_id: topic_ids)
695+
.includes(:mailing_list)
696+
.group_by(&:topic_id)
697+
.transform_values { |tmls| tmls.map(&:mailing_list) }
698+
end
699+
673700
def preload_topic_participants
674701
topic_ids = @topics.map(&:id)
675702
return if topic_ids.empty?

app/helpers/topics_helper.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ def team_readers_icon_html(topic:, readers:)
227227
end
228228
end
229229

230+
def mailing_list_badges_html(mailing_lists)
231+
return "".html_safe if mailing_lists.blank?
232+
233+
badges = mailing_lists.map do |ml|
234+
tag.span(ml.display_name, class: "mailing-list-badge", title: ml.identifier)
235+
end
236+
237+
tag.span(class: "mailing-list-badges") { safe_join(badges) }
238+
end
239+
230240
def topic_title_link(topic)
231241
if user_signed_in? && current_user.open_threads_at_first_unread?
232242
topic_path(topic, anchor: "first-unread")

app/models/mailing_list.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class MailingList < ApplicationRecord
2+
has_many :message_mailing_lists, dependent: :destroy
3+
has_many :messages, through: :message_mailing_lists
4+
has_many :topic_mailing_lists, dependent: :destroy
5+
has_many :topics, through: :topic_mailing_lists
6+
7+
validates :identifier, presence: true, uniqueness: true
8+
validates :display_name, presence: true
9+
end

app/models/message.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class Message < ApplicationRecord
1010
has_many :mentions
1111
has_many :mentioned_aliases, through: :mentions, source: :alias
1212
has_many :notes
13+
has_many :message_mailing_lists, dependent: :destroy
14+
has_many :mailing_lists, through: :message_mailing_lists
1315

1416
validates :subject, presence: true
1517
# Body may be blank for some historical imports; allow blank but keep presence on subject.

app/models/message_mailing_list.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class MessageMailingList < ApplicationRecord
2+
belongs_to :message
3+
belongs_to :mailing_list
4+
5+
after_create :ensure_topic_mailing_list
6+
7+
private
8+
9+
def ensure_topic_mailing_list
10+
TopicMailingList.find_or_create_by!(
11+
topic_id: message.topic_id,
12+
mailing_list_id: mailing_list_id
13+
)
14+
end
15+
end

app/models/topic.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class Topic < ApplicationRecord
2929
has_many :topics_merged_into_this, class_name: "Topic", foreign_key: :merged_into_topic_id
3030
has_one :topic_merge_as_source, class_name: "TopicMerge", foreign_key: :source_topic_id
3131
has_many :topic_merges_as_target, class_name: "TopicMerge", foreign_key: :target_topic_id
32+
has_many :topic_mailing_lists, dependent: :destroy
33+
has_many :mailing_lists, through: :topic_mailing_lists
3234

3335
scope :active, -> { where(merged_into_topic_id: nil) }
3436
scope :merged, -> { where.not(merged_into_topic_id: nil) }

app/models/topic_mailing_list.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class TopicMailingList < ApplicationRecord
2+
belongs_to :topic
3+
belongs_to :mailing_list
4+
end

0 commit comments

Comments
 (0)