Skip to content

Commit 171bc29

Browse files
committed
Prepare only required Discord roles during sync
1 parent 1fe2725 commit 171bc29

4 files changed

Lines changed: 70 additions & 19 deletions

File tree

lib/polish_open_source_rank/contexts/community/application/sync_discord_connection.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ def initialize(sync_job_repository:, profile_read_model:, access_read_model:, me
1919
end
2020

2121
def call(period_start:, limit: 10)
22-
prepared_roles = role_map.prepare(period_start: period_start)
23-
sync_job_repository.pending(limit: limit).each do |job|
22+
jobs = sync_job_repository.pending(limit: limit)
23+
prepared_roles = prepare_roles(jobs, period_start)
24+
jobs.each do |job|
2425
sync_job(SyncContext.new(job: job, period_start: period_start, prepared_roles: prepared_roles))
2526
end
2627
end
2728

2829
def call_for(platform:, source_id:, period_start:)
29-
prepared_roles = role_map.prepare(period_start: period_start)
30-
sync_job_repository.pending_for(platform, source_id).each do |job|
30+
jobs = sync_job_repository.pending_for(platform, source_id)
31+
prepared_roles = prepare_roles(jobs, period_start)
32+
jobs.each do |job|
3133
sync_job(SyncContext.new(job: job, period_start: period_start, prepared_roles: prepared_roles))
3234
end
3335
end
@@ -36,6 +38,13 @@ def call_for(platform:, source_id:, period_start:)
3638

3739
attr_reader :access_read_model, :member_gateway, :profile_read_model, :role_map, :sync_job_repository
3840

41+
def prepare_roles(jobs, period_start)
42+
role_map.prepare(
43+
period_start: period_start,
44+
role_keys: jobs.flat_map { |job| access(job, period_start).fetch(:role_keys) }.uniq
45+
)
46+
end
47+
3948
def sync_job(context)
4049
job = context.job
4150
case job.fetch(:action_kind)

lib/polish_open_source_rank/contexts/community/infrastructure/discord/discord_role_map.rb

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,19 @@ def initialize(gateway: nil, published_language_source: nil,
4444
@role_catalog = role_catalog
4545
end
4646

47-
def prepare(period_start:)
47+
def prepare(period_start:, role_keys: nil)
4848
static_role_ids = static_role_ids(GLOBAL_KEYS + city_role_keys)
4949
return PreparedRoles.new(managed_role_ids: static_role_ids, role_ids_by_key: {}) unless gateway
5050

51-
languages = published_languages(period_start)
51+
dynamic_role_keys = dynamic_role_keys(period_start, role_keys)
52+
if dynamic_role_keys.empty?
53+
return PreparedRoles.new(managed_role_ids: static_role_ids, role_ids_by_key: {})
54+
end
55+
5256
state = provisioning_state
53-
ensure_language_resources(languages, state)
57+
ensure_language_resources(dynamic_role_keys, state)
5458

55-
build_prepared_roles(static_role_ids, dynamic_role_ids_by_key(languages, state.roles))
59+
build_prepared_roles(static_role_ids, dynamic_role_ids_by_key(dynamic_role_keys, state.roles))
5660
end
5761

5862
def managed_role_ids(prepared: nil)
@@ -102,11 +106,14 @@ def build_prepared_roles(static_role_ids, role_ids_by_key)
102106
)
103107
end
104108

105-
def ensure_language_resources(languages, state)
106-
languages.each do |language|
107-
ensure_language_role_and_channel(Domain::DiscordLanguageRoleKey.build_open(language), state)
108-
ensure_language_role_and_channel(Domain::DiscordLanguageRoleKey.build_top(language), state)
109-
end
109+
def dynamic_role_keys(period_start, role_keys)
110+
return role_keys.filter { |key| Domain::DiscordLanguageRoleKey.new(key).dynamic? }.uniq if role_keys
111+
112+
language_role_keys(published_languages(period_start))
113+
end
114+
115+
def ensure_language_resources(role_keys, state)
116+
role_keys.each { |role_key| ensure_language_role_and_channel(role_key, state) }
110117
end
111118

112119
def ensure_language_role_and_channel(role_key, state)
@@ -158,9 +165,9 @@ def ensure_dynamic_channel(role_key, role_id, state)
158165
state.channel_creation_disabled = true
159166
end
160167

161-
def dynamic_role_ids_by_key(languages, roles)
168+
def dynamic_role_ids_by_key(role_keys, roles)
162169
roles_by_name = roles.to_h { |role| [role.fetch('name'), role.fetch('id')] }
163-
language_role_keys(languages).to_h do |key|
170+
role_keys.to_h do |key|
164171
[key, roles_by_name[role_catalog.role_name(key)]]
165172
end.compact
166173
end

spec/polish_open_source_rank/contexts/community/application/sync_discord_connection_spec.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ def discord_access(*) = { role_keys: %w[top] }
99
end
1010

1111
class RoleMapForSync
12+
attr_reader :prepared_role_keys
13+
1214
Prepared = Struct.new(:managed_role_ids, :role_ids_by_key, keyword_init: true) do
1315
def role_ids(keys)
1416
keys.filter_map { |key| role_ids_by_key.fetch(key, nil) }
1517
end
1618
end
1719

18-
def prepare(*)
20+
def prepare(period_start: nil, role_keys: nil)
21+
raise ArgumentError, 'missing period start' unless period_start
22+
23+
@prepared_role_keys = role_keys
1924
Prepared.new(managed_role_ids: %w[role-top role-old], role_ids_by_key: { 'top' => 'role-top' })
2025
end
2126

@@ -73,6 +78,7 @@ def sync_joined_member(**)
7378
it 'syncs only the requested connected account' do
7479
repository = seeded_repository
7580
gateway = MemberGatewayForSync.new
81+
role_map = RoleMapForSync.new
7682
repository.request_oauth_sync(
7783
platform: 'github',
7884
source_id: 1,
@@ -91,8 +97,10 @@ def sync_joined_member(**)
9197
welcome_channel_id: nil
9298
)
9399

94-
use_case(repository, gateway).call_for(platform: 'github', source_id: 1, period_start: '2026-04-01')
100+
use_case(repository, gateway, role_map: role_map)
101+
.call_for(platform: 'github', source_id: 1, period_start: '2026-04-01')
95102

103+
expect(role_map.prepared_role_keys).to eq(%w[top])
96104
expect(gateway.synced).to include(discord_user_id: 'discord-1', access_token: 'access-token')
97105
expect(repository.sync_status('github', 1)).to eq('synced')
98106
expect(repository.sync_status('github', 2)).to eq('pending')
@@ -115,13 +123,13 @@ def sync_joined_member(**)
115123
expect(repository.sync_status('github', 1)).to eq('failed')
116124
end
117125

118-
def use_case(repository, gateway)
126+
def use_case(repository, gateway, role_map: RoleMapForSync.new)
119127
described_class.new(
120128
sync_job_repository: repository,
121129
profile_read_model: ProfileReadModelForSync.new,
122130
access_read_model: AccessReadModelForSync.new,
123131
member_gateway: gateway,
124-
role_map: RoleMapForSync.new
132+
role_map: role_map
125133
)
126134
end
127135

spec/polish_open_source_rank/contexts/community/infrastructure/discord/discord_role_map_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,33 @@
8080
end).to eq(%w[Languages ruby top-100-ruby])
8181
end
8282

83+
it 'provisions only requested dynamic language roles when role keys are provided' do
84+
prepared = map.prepare(
85+
period_start: '2026-04-01',
86+
role_keys: ['DISCORD_ROLE_LANGUAGE:ruby:Ruby']
87+
)
88+
89+
expect(prepared.role_ids_by_key).to eq('DISCORD_ROLE_LANGUAGE:ruby:Ruby' => 'role-1')
90+
expect(gateway_data.fetch(:created_roles).map { |role| role.fetch('name') }).to eq(['Ruby'])
91+
expect(gateway_data.fetch(:created_channels).map do |channel|
92+
channel.fetch('name')
93+
end).to eq(%w[Languages ruby])
94+
end
95+
96+
it 'skips dynamic provisioning when requested role keys are static' do
97+
ENV['DISCORD_ROLE_TOP_100_PL'] = 'country-role'
98+
99+
prepared = map.prepare(
100+
period_start: '2026-04-01',
101+
role_keys: ['DISCORD_ROLE_TOP_100_PL']
102+
)
103+
104+
expect(prepared.managed_role_ids).to include('country-role')
105+
expect(prepared.role_ids_by_key).to be_empty
106+
expect(gateway_data.fetch(:created_roles)).to be_empty
107+
expect(gateway_data.fetch(:created_channels)).to be_empty
108+
end
109+
83110
context 'when the Languages category already exists' do
84111
let(:initial_channels) do
85112
[{ 'id' => 'category-1', 'name' => 'Languages', 'type' => 4, 'permission_overwrites' => [] }]

0 commit comments

Comments
 (0)