Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fc8e087
feat: introduces country_lanaguage_preference endpoint
mohsinayoob Sep 23, 2024
4faac26
fix: fixed logic getting preferences for country and language
mohsinayoob Sep 23, 2024
d727464
fix: fixed relations with country_language_preference model
mohsinayoob Sep 25, 2024
8f82a55
fix: resolved gem for countries
mohsinayoob Sep 25, 2024
d95102f
fix: some optimizations
mohsinayoob Sep 25, 2024
3f940ac
Merge pull request #687 from quran/feat/country-language-preference
osamasayed Oct 1, 2024
524eda2
QF-1475 filter by permission to share
osamasayed Dec 28, 2024
3b97d98
Allowed to share audio
osamasayed Dec 29, 2024
f421160
Merge pull request #698 from quran/QF-1475-permission-to-share
osamasayed Jan 1, 2025
f15d8ce
Update whitelisted domains
osamasayed Feb 5, 2025
1cfc5ef
Add configs
osamasayed May 17, 2025
a9fd514
Setup Sentry for non-production envs
osamasayed Jun 5, 2025
39a2d15
handle empty translated name of recitations
osamasayed Jun 5, 2025
de4269c
Fix language preferences
osamasayed Aug 25, 2025
01ea25b
update API logic to accept empty country as per specs
osamasayed Aug 27, 2025
ad18cf7
Optimize API call
osamasayed Aug 30, 2025
c58b9fa
Merge pull request #729 from quran/QF-921
osamasayed Aug 30, 2025
6515c4d
add QR country configs fields
osamasayed Sep 9, 2025
10114a7
Merge pull request #730 from quran/QF-1798
osamasayed Sep 9, 2025
acea928
Support transliterated_name of chapters
osamasayed Sep 11, 2025
4fd514a
Merge pull request #731 from quran/QF-3316
osamasayed Sep 11, 2025
f4c955a
Add ready and health APIs
osamasayed Sep 13, 2025
89dfce3
Add chapter metadata API with associated models, presenters, and tests
yousefdergham Oct 5, 2025
fd338f6
Refactor chapter metadata handling by updating parameter names, impro…
yousefdergham Oct 7, 2025
31d17d1
Refactor chapter metadata controller and presenter to remove locale p…
yousefdergham Oct 7, 2025
d38d00e
fix: resolve cors issue for qr
mohsinayoob Oct 7, 2025
febb520
fix: typo
mohsinayoob Oct 7, 2025
6c5b69c
Merge pull request #739 from quran/cors-issue
osamasayed Oct 7, 2025
ae205fc
Refactor: chapter metadata finder
yousefdergham Oct 7, 2025
ba2e3d4
Refactor: update permitted parameters in chapter metadata controller …
yousefdergham Oct 7, 2025
870d7b7
Refactor: streamline chapter metadata handling by introducing a prese…
yousefdergham Oct 8, 2025
efed618
Refactor: simplify before hooks in chapter metadata specs by removing…
yousefdergham Oct 8, 2025
3057492
Add qr_default_arabic_fonts
osamasayed Oct 13, 2025
3d05d42
Merge pull request #740 from quran/QF-3513
osamasayed Oct 13, 2025
138c567
guards against out of bound chapter numbers
osamasayed Oct 13, 2025
6593e59
Merge pull request #737 from quran/QF-3119-chapter-metadata
osamasayed Oct 13, 2025
8dd5c6c
link to audio recitations instead of reciter
osamasayed Oct 14, 2025
0cb1f18
Merge pull request #742 from quran/QF-3532
osamasayed Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ public/sitemaps
/config/credentials/production.key

/config/credentials/development.key

.env
Comment on lines 36 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Broaden secrets ignore patterns and keep a checked-in example.

Avoid leaking future credentials keys and env variants.

Apply:

-/config/credentials/production.key
-
-/config/credentials/development.key
+/# ignore all Rails credentials keys
+/config/credentials/*.key
+
+# allow a sample env file but ignore real envs
+.env
+.env.*
+!.env.example
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/config/credentials/production.key
/config/credentials/development.key
.env
# ignore all Rails credentials keys
/config/credentials/*.key
# allow a sample env file but ignore real envs
.env
.env.*
!.env.example
🤖 Prompt for AI Agents
In .gitignore around lines 36-40 replace the specific secret file entries with
broader ignore patterns to avoid leaking future keys and variants: add
/config/credentials/*.key to cover all credential keys, ignore all env variants
with .env* but explicitly allow a checked-in example by adding !.env.example (or
other example filename you use), and remove the hard-coded individual filenames;
ensure the example file is committed so consumers have a template.

1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ gem 'tzinfo-data'

# Exception tracking
gem 'sentry-raven', group: [:production]
gem 'countries', '= 6.0.1'

group :development, :test do
gem 'pry-rails'
Expand Down
5 changes: 4 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ GEM
coderay (1.1.3)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
countries (6.0.1)
unaccent (~> 0.3)
crass (1.0.6)
dead_end (3.1.1)
derailed_benchmarks (2.1.1)
Expand Down Expand Up @@ -370,6 +372,7 @@ GEM
concurrent-ruby (~> 1.0)
tzinfo-data (1.2022.1)
tzinfo (>= 1.0.0)
unaccent (0.4.0)
unicode-display_width (2.1.0)
uniform_notifier (1.16.0)
webrick (1.7.0)
Expand All @@ -389,7 +392,7 @@ DEPENDENCIES
benchmark-ips
bullet
byebug
cld3 (= 3.4.3)
countries (= 6.0.1)
derailed_benchmarks
elastic-transport
elasticsearch-model
Expand Down
17 changes: 17 additions & 0 deletions app/controllers/api/qdc/chapter_metadata_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Api::Qdc
class ChapterMetadataController < ApiController
before_action :init_presenter

def metadata
render
end

protected

def init_presenter
@presenter = Qdc::ChapterMetadataPresenter.new(params)
end
end
end
13 changes: 13 additions & 0 deletions app/controllers/api/qdc/quran_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,18 @@

module Api::Qdc
class QuranController < Api::V4::QuranController
def tafsir
@presenter = Qdc::TafsirsPresenter.new(params)
filters = resource_filters(@resource)
@filter_names = humanize_filter_names(filters)

@tafsirs = if (@resource = fetch_tafsir_resource)
Tafsir.order('verse_id ASC').where(filters)
else
[]
end

render
end
Comment on lines +5 to +17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: filters computed before @resource is set, dropping resource_content_id

resource_filters(@resource) runs with nil, so queries miss resource scoping.

Move filter construction after assigning @resource (and build @filter_names from the corrected filters):

   def tafsir
-      @presenter = Qdc::TafsirsPresenter.new(params)
-      filters = resource_filters(@resource)
-      @filter_names = humanize_filter_names(filters)
-
-      @tafsirs = if (@resource = fetch_tafsir_resource)
-                   Tafsir.order('verse_id ASC').where(filters)
-                 else
-                   []
-                 end
+      @presenter = Qdc::TafsirsPresenter.new(params)
+      if (@resource = fetch_tafsir_resource)
+        filters = resource_filters(@resource)
+        @filter_names = humanize_filter_names(filters)
+        @tafsirs = Tafsir.where(filters).order(verse_id: :asc)
+      else
+        @filter_names = {}
+        @tafsirs = []
+      end
       render
   end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def tafsir
@presenter = Qdc::TafsirsPresenter.new(params)
filters = resource_filters(@resource)
@filter_names = humanize_filter_names(filters)
@tafsirs = if (@resource = fetch_tafsir_resource)
Tafsir.order('verse_id ASC').where(filters)
else
[]
end
render
end
def tafsir
@presenter = Qdc::TafsirsPresenter.new(params)
if (@resource = fetch_tafsir_resource)
filters = resource_filters(@resource)
@filter_names = humanize_filter_names(filters)
@tafsirs = Tafsir.where(filters).order(verse_id: :asc)
else
@filter_names = {}
@tafsirs = []
end
render
end
🤖 Prompt for AI Agents
In app/controllers/api/qdc/quran_controller.rb around lines 5 to 17,
resource_filters(@resource) is called before @resource is assigned so the
computed filters omit resource_content_id; move the filters =
resource_filters(@resource) line (and the subsequent @filter_names =
humanize_filter_names(filters)) to after the @resource = fetch_tafsir_resource
assignment and use the resulting filters when building @tafsirs (so filters
reflect the real @resource), leaving render as-is.

end
end
4 changes: 4 additions & 0 deletions app/controllers/api/qdc/recitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

module Api::Qdc
class RecitationsController < Api::V4::RecitationsController
protected
def init_presenter
@presenter = Qdc::RecitationsPresenter.new(params)
end
end
end
107 changes: 107 additions & 0 deletions app/controllers/api/qdc/resources_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,113 @@ def languages
render
end

def country_language_preference
user_device_language = request.query_parameters[:user_device_language].presence
country = request.query_parameters[:country].presence&.upcase

# Require a valid user_device_language always
if user_device_language.blank?
return render_bad_request('user_device_language is required')
end

Comment on lines +119 to +127
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Normalize language param; ensure case-insensitive matching

Downcase the ISO code before validation to avoid false negatives.

-      user_device_language = request.query_parameters[:user_device_language].presence
+      user_device_language = request.query_parameters[:user_device_language].to_s.downcase.presence
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def country_language_preference
user_device_language = request.query_parameters[:user_device_language].presence
country = request.query_parameters[:country].presence&.upcase
# Require a valid user_device_language always
if user_device_language.blank?
return render_bad_request('user_device_language is required')
end
def country_language_preference
user_device_language = request.query_parameters[:user_device_language].to_s.downcase.presence
country = request.query_parameters[:country].presence&.upcase
# Require a valid user_device_language always
if user_device_language.blank?
return render_bad_request('user_device_language is required')
end
🤖 Prompt for AI Agents
In app/controllers/api/qdc/resources_controller.rb around lines 119 to 127, the
user_device_language ISO code must be normalized for case-insensitive matching;
change the assignment so you convert the incoming param to a string, strip
whitespace and downcase it before presence validation (e.g. use
request.query_parameters[:user_device_language].to_s.strip.presence&.downcase or
equivalent) and then use that normalized variable for the blank check and any
subsequent validation/lookup.

unless Language.exists?(iso_code: user_device_language)
return render_bad_request('Invalid user_device_language')
end

# Validate country only if provided
if country.present?
valid_countries = ISO3166::Country.all.map(&:alpha2)
unless valid_countries.include?(country)
return render_bad_request('Invalid country code')
end
Comment on lines +133 to +137
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optimize country validation

Avoid allocating all countries on each request; use a direct lookup.

-        valid_countries = ISO3166::Country.all.map(&:alpha2)
-        unless valid_countries.include?(country)
+        unless ISO3166::Country.find_country_by_alpha2(country)
           return render_bad_request('Invalid country code')
         end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if country.present?
valid_countries = ISO3166::Country.all.map(&:alpha2)
unless valid_countries.include?(country)
return render_bad_request('Invalid country code')
end
if country.present?
unless ISO3166::Country.find_country_by_alpha2(country)
return render_bad_request('Invalid country code')
end
🤖 Prompt for AI Agents
In app/controllers/api/qdc/resources_controller.rb around lines 133 to 137, the
code builds ISO3166::Country.all.map(&:alpha2) on every request which allocates
the entire country list; replace this with a direct lookup such as using
ISO3166::Country.find_country_by_alpha2(country) (or ISO3166::Country[country])
and check whether that returns nil to determine validity, rendering the same
bad-request response if the lookup fails.

end

if country.present?
# First try to find country-specific preference
preferences = CountryLanguagePreference.with_includes
.where(user_device_language: user_device_language, country: country)
@preference = preferences.first

# If no country-specific preference found, try global preference
unless @preference
@preference = CountryLanguagePreference.with_includes
.find_by(user_device_language: user_device_language, country: nil)
end
else
# No country provided: search by user_device_language only
# Prefer global (country: nil), then fall back to any match for that language
@preference = CountryLanguagePreference.with_includes
.find_by(user_device_language: user_device_language, country: nil)

unless @preference
@preference = CountryLanguagePreference.with_includes
.where(user_device_language: user_device_language)
.first
end
end

if @preference
# Filter out unapproved resources when building the response
@data = build_preference_data(@preference)
render
else
render_404("No matching country language preference found")
end
end
Comment on lines +119 to +171
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

End-to-end verification suggestion

After applying the gating changes, verify no QDC/V4 endpoints return rejected resources.


🏁 Script executed:

#!/bin/bash
# Find ResourceContent queries missing allowed_to_share in controllers/presenters
rg -nP --type=ruby -C2 "(ResourceContent|Recitation).*approved" app/controllers app/presenters \
  | sed -n '1,200p'

echo "---- Potential misses (heuristic):"
rg -nP --type=ruby -C2 "(ResourceContent|Recitation).*\\.(eager_load|includes|joins)\\(.*\\).*\\.(translations|tafsirs|chapter_info|media|one_verse|one_chapter)" app/controllers app/presenters \
  | rg -v "allowed_to_share" -n -C2

Length of output: 5273


Add allowed_to_share scope to QDC resource queries

  • In app/controllers/api/qdc/resources_controller.rb:44, change
    ResourceContent
      .eager_load(:translated_name)
      .approved
      .one_word
      .translations_only
    to
    ResourceContent
      .eager_load(:translated_name)
      .approved
      .allowed_to_share
      .one_word
      .translations_only
  • In the default translations block (…; default_translations: ids.any? ? ResourceContent.where(id: ids).approved.includes(:translated_name) : []), insert .allowed_to_share after .approved.

Re-run the end-to-end check to confirm no QDC/V4 endpoints surface rejected resources.

🤖 Prompt for AI Agents
In app/controllers/api/qdc/resources_controller.rb around lines 119-171, the
resource queries used when building preference responses are missing the
allowed_to_share scope; update the ResourceContent query chains so that
.allowed_to_share is inserted immediately after .approved (e.g. .approved.then
.allowed_to_share.then the rest of the chain) for both the main resource query
and the default_translations branch (the conditional that builds
default_translations using ResourceContent.where(id:
ids).approved.includes(:translated_name) should become
.approved.allowed_to_share.includes(:translated_name)); rerun the end-to-end
checks to confirm rejected resources are excluded.


private

def build_preference_data(preference)
# Sanitize CSV IDs for default translations
ids = if preference.default_translation_ids.present?
preference.default_translation_ids
.split(',')
.map(&:strip)
.reject(&:blank?)
.map(&:to_i)
else
[]
end

# QR specific default translations ids
qr_ids = if preference.qr_default_translations_ids.present?
preference.qr_default_translations_ids
.split(',')
.map(&:strip)
.reject(&:blank?)
.map(&:to_i)
else
[]
end

# QR default arabic fonts
qr_font_ids = if preference.qr_default_arabic_fonts.present?
preference.qr_default_arabic_fonts
.split(',')
.map(&:strip)
.reject(&:blank?)
.map(&:to_i)
else
[]
end

{
preference: preference,
default_mushaf: preference.mushaf&.enabled ? preference.mushaf : nil,
default_translations: ids.any? ?
ResourceContent.where(id: ids).approved.includes(:translated_name) : [],
qr_default_translations: qr_ids.any? ?
ResourceContent.where(id: qr_ids).approved.includes(:translated_name) : [],
default_tafsir: preference.tafsir&.approved? ? preference.tafsir : nil,
default_wbw_language: preference.wbw_language,
default_reciter: preference.reciter,
ayah_reflections_languages: Language.where(iso_code: preference.ayah_reflections_languages&.split(',') || []),
qr_reflection_languages: Language.where(iso_code: preference.qr_reflection_languages&.split(',') || []),
learning_plan_languages: Language.where(iso_code: preference.learning_plan_languages&.split(',') || []),
qr_default_arabic_fonts: qr_font_ids
}
end

protected

def load_translations
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/api/qdc/tafsirs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

module Api::Qdc
class TafsirsController < Api::V4::TafsirsController
protected
def init_presenter
@presenter = Qdc::TafsirsPresenter.new(params)
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/api/qdc/translations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TranslationsController < Api::V4::TranslationsController

protected
def init_presenter
@presenter = TranslationsPresenter.new(params)
@presenter = Qdc::TranslationsPresenter.new(params)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Confirm presenter-side param mutation is intentional.

Qdc::TranslationsPresenter may mutate params (e.g., resource_id). Verify downstream usage in this controller relies on the mutated params; otherwise consider passing a copy to avoid side effects.

🤖 Prompt for AI Agents
In app/controllers/api/qdc/translations_controller.rb around line 10, the
controller passes the request params directly into Qdc::TranslationsPresenter
which may mutate params (e.g., resource_id); confirm whether downstream
controller actions rely on those mutations and if not, prevent side effects by
passing a duped/filtered copy (e.g., params.to_unsafe_h.deep_dup or
params.permit(...).to_h) into the presenter or update the presenter to not
mutate its input. If mutations are intentional, add a comment explaining
expected side effects; otherwise replace the direct params with a defensive copy
when instantiating the presenter.

end

def load_translations
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/api/v3/options_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def translations
.one_verse
.translations
.approved
.allowed_to_share
.order('priority ASC')

@translations = eager_load_translated_name(list)
Expand All @@ -47,6 +48,7 @@ def chapter_info
.chapter_info
.one_chapter
.approved
.allowed_to_share

@chapter_infos = eager_load_translated_name(list)
render
Expand All @@ -58,6 +60,7 @@ def tafsirs
.eager_load(:translated_name)
.tafsirs
.approved
.allowed_to_share
.order('priority ASC')

@tafsirs = eager_load_translated_name(list)
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/v3/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def translations

approved_translations = ResourceContent
.approved
.allowed_to_share
.translations
.one_verse

Expand Down
5 changes: 4 additions & 1 deletion app/controllers/api/v3/tafsirs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ def verse
end

def tafisr_id
approved_tafsir = ResourceContent.tafsirs.approved
approved_tafsir = ResourceContent
.tafsirs
.approved
.allowed_to_share

tafsir = approved_tafsir.where(id: params[:tafsir])
.or(approved_tafsir.where(slug: params[:tafsir]))
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/api/v4/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def fetch_translation_resource
.translations
.one_verse
.approved
.allowed_to_share

find_resource(approved, params[:translation_id], true)
end
Expand All @@ -20,6 +21,7 @@ def fetch_tafsir_resource
.tafsirs
.one_verse
.approved
.allowed_to_share

find_resource(approved, params[:tafsir_id], true)
end
Expand Down
13 changes: 10 additions & 3 deletions app/controllers/api/v4/resources_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def translations
.eager_load(:translated_name)
.one_verse
.translations
.allowed_to_share
.approved
.order('priority ASC')

Expand All @@ -31,7 +32,7 @@ def translation_info
end

def word_by_word_translations
list = ResourceContent.eager_load(:translated_name).approved.one_word.translations_only.order('priority ASC')
list = ResourceContent.eager_load(:translated_name).approved.allowed_to_share.one_word.translations_only.order('priority ASC')

@word_by_word_translations = eager_load_translated_name(list)

Expand All @@ -43,6 +44,7 @@ def tafsirs
.eager_load(:translated_name)
.tafsirs
.approved
.allowed_to_share
.order('priority ASC')

@presenter = ResourcePresenter.new(params)
Expand All @@ -62,6 +64,8 @@ def tafsir_info
def recitations
list = Recitation
.eager_load(reciter: :translated_name)
.joins(:resource_content)
.where.not(resource_contents: { permission_to_share: :rejected })
.approved
Comment on lines +67 to 69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prefer merging the ResourceContent scope over duplicating the WHERE

Avoid re-encoding scope logic; use merge(ResourceContent.allowed_to_share).

-               .joins(:resource_content)
-               .where.not(resource_contents: { permission_to_share: :rejected })
+               .joins(:resource_content)
+               .merge(ResourceContent.allowed_to_share)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.joins(:resource_content)
.where.not(resource_contents: { permission_to_share: :rejected })
.approved
.joins(:resource_content)
.merge(ResourceContent.allowed_to_share)
.approved
🤖 Prompt for AI Agents
In app/controllers/api/v4/resources_controller.rb around lines 67 to 69, the
query duplicates ResourceContent permission logic using
.where.not(resource_contents: { permission_to_share: :rejected }); replace that
clause with .merge(ResourceContent.allowed_to_share) so the controller reuses
the model scope, ensuring the join to :resource_content remains and the final
.approved scope is kept.

.order('translated_names.language_priority desc')

Expand Down Expand Up @@ -93,6 +97,7 @@ def chapter_infos
.chapter_info
.one_chapter
.approved
.allowed_to_share

@chapter_infos = eager_load_translated_name(list)

Expand All @@ -103,8 +108,9 @@ def verse_media
@media = ResourceContent
.includes(:language)
.media
.one_verse.approved

.one_verse
.approved
.allowed_to_share
render
end

Expand All @@ -121,6 +127,7 @@ def changes
.change_log(after: time)
.filter_subtype(params[:type])
.approved
.allowed_to_share

render
else
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/v4/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def translations
.approved
.translations
.one_verse
.allowed_to_share

params[:translations] = approved_translations
.where(id: translation)
Expand Down
12 changes: 7 additions & 5 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ def fetch_locale
end

def set_cache_headers
if action_name != 'random'
expires_in 7.day, public: true
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
end
headers['Cache-Control'] = 'no-store, no-cache, max-age=0, private, must-revalidate'
headers['Pragma'] = 'no-cache'
headers['Expires'] = '0'

Comment on lines +42 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t blanket “no-store”; this kills CDN/browser caching for all endpoints.

Prefer targeted no-cache or conditional policy to avoid a perf regression on heavy GETs.

Apply scoped caching and keep no-store only when required:

-    headers['Cache-Control'] = 'no-store, no-cache, max-age=0, private, must-revalidate'
-    headers['Pragma'] = 'no-cache'
-    headers['Expires'] = '0'
+    if request.method != 'GET' || params[:no_cache].present?
+      headers['Cache-Control'] = 'no-store, no-cache, max-age=0, private, must-revalidate'
+      headers['Pragma'] = 'no-cache'
+      headers['Expires'] = '0'
+    else
+      # cacheable GETs; tune s-maxage per endpoint if needed
+      headers['Cache-Control'] = 'public, max-age=0, s-maxage=300, stale-while-revalidate=300'
+      headers['Vary'] = ['Origin', headers['Vary']].compact.join(', ')
+    end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
headers['Cache-Control'] = 'no-store, no-cache, max-age=0, private, must-revalidate'
headers['Pragma'] = 'no-cache'
headers['Expires'] = '0'
if request.method != 'GET' || params[:no_cache].present?
headers['Cache-Control'] = 'no-store, no-cache, max-age=0, private, must-revalidate'
headers['Pragma'] = 'no-cache'
headers['Expires'] = '0'
else
# cacheable GETs; tune s-maxage per endpoint if needed
headers['Cache-Control'] = 'public, max-age=0, s-maxage=300, stale-while-revalidate=300'
headers['Vary'] = ['Origin', headers['Vary']].compact.join(', ')
end

# Keep HSTS header
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Gate HSTS to HTTPS production only.

Avoid setting HSTS on HTTP, dev, or staging domains not ready for preload.

-    headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
+    if Rails.env.production? && request.ssl?
+      headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
+    end

Verify your edge/proxy isn’t already injecting HSTS to prevent duplicate/conflicting headers.


🏁 Script executed:

#!/bin/bash
# Find HSTS header assignment in ApplicationController
rg -n "Strict-Transport-Security" -n app/controllers/application_controller.rb
# Show context around the HSTS header assignment
sed -n '30,80p' app/controllers/application_controller.rb
# Check if force_ssl is enabled in production
grep -R "force_ssl" -n config/environments/production.rb

Length of output: 1663


🏁 Script executed:

#!/bin/bash
# Search for HSTS or Strict-Transport-Security in config directories
rg -n "hsts" -n config || true
rg -n "Strict-Transport-Security" -n config || true

# Check for secure_headers gem usage
rg -n "secure_headers" -n Gemfile* || true
rg -n "secure_headers" -n Gemfile.lock || true

# Look for HSTS configuration in initializers
rg -n "hsts" -n config/initializers || true
rg -n "secure_headers" -n config/initializers || true

Length of output: 627


Gate HSTS header to production over HTTPS only
In set_cache_headers (app/controllers/application_controller.rb:47), wrap the HSTS assignment in a guard:

-    headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
+    if Rails.env.production? && request.ssl?
+      headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
+    end

Ensure your edge or CDN isn’t already injecting HSTS to avoid duplicate/conflicting headers.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
if Rails.env.production? && request.ssl?
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
end
🤖 Prompt for AI Agents
In app/controllers/application_controller.rb around line 47, the HSTS header is
unconditionally set; restrict it to production over HTTPS only and avoid
overwriting an existing header by wrapping the assignment in a guard such as if
Rails.env.production? && request.ssl? &&
headers['Strict-Transport-Security'].blank? so the header is only added in
production requests served over TLS and only when not already present (confirm
your edge/CDN isn’t already injecting HSTS to prevent duplicates).


# Keep CORS header
headers['Access-Control-Allow-Origin'] = '*'
end
Comment on lines 50 to 51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

CORS: add Vary and basic preflight headers (or delegate to Rack::Cors).

With "*" you should set Vary: Origin to protect shared caches; consider methods/headers too.

     headers['Access-Control-Allow-Origin'] = '*'
+    headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
+    headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
+    headers['Vary'] = ['Origin', headers['Vary']].compact.join(', ')

If you need credentials, replace "*" with the request Origin echo and set Allow-Credentials: true.


🏁 Script executed:

#!/bin/bash
echo "Searching for Rack::Cors usage..."
rg -n "rack-cors" || true

echo "Searching for gem 'rack-cors' in Gemfile..."
grep -R "rack-cors" -n Gemfile* || true

echo "Searching for CORS configuration in initializers..."
grep -R "cors" -n config/initializers || true

echo "Searching routes for OPTIONS preflight handling..."
grep -R "match.*options" -n config/routes.rb || true

echo "Inspecting application_controller.rb around CORS headers..."
rg -n -C3 "Access-Control-Allow-Origin" app/controllers/application_controller.rb || true

Length of output: 1343


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' config/initializers/cros.rb

Length of output: 528


Remove manual CORS headers in ApplicationController
The app is already using Rack::Cors (see config/initializers/cros.rb) to handle Access-Control headers and preflight; delete the headers['Access-Control-Allow-Origin'] = '*' (and any custom CORS logic) around line 50 of app/controllers/application_controller.rb and, if you need to tweak allowed methods, headers, or add Vary: Origin, update the Rack::Cors initializer instead.

🤖 Prompt for AI Agents
In app/controllers/application_controller.rb around lines 50 to 51, remove the
manual CORS header assignment headers['Access-Control-Allow-Origin'] = '*' and
any other custom CORS logic in this controller; rely on Rack::Cors configured in
config/initializers/cors.rb for Access-Control headers and preflight handling
instead, and if you need to adjust allowed origins, methods, headers, or add
Vary: Origin, make those changes in the Rack::Cors initializer rather than
setting headers in the controller.


Expand Down
8 changes: 8 additions & 0 deletions app/controllers/health_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class HealthController < ApplicationController
# Lightweight liveness endpoint: confirms app process & middleware stack respond.
def show
render json: { status: 'ok' }
end
Comment on lines +3 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Skip heavy filters (auth, locale, rate limiting) if any.

Health should bypass non-essential before_actions to stay fast/reliable.

Run to check for inherited filters and add skips if present:

If needed:

 class HealthController < ApplicationController
+  skip_before_action :authenticate_user!, :set_locale, :verify_authenticity_token, raise: false
   def show
     render json: { status: 'ok' }
   end
 end

🏁 Script executed:

#!/bin/bash
rg -nC2 -g 'app/controllers/**/*.rb' -P 'class\s+ApplicationController\b|before_action|around_action'

Length of output: 10266


Skip non-essential ApplicationController before_actions for the /health endpoint

ApplicationController registers before_action :set_cache_headers and :set_default_response_format — add skip_before_action :set_cache_headers, :set_default_response_format, raise: false in app/controllers/health_controller.rb so /health remains minimal (see app/controllers/application_controller.rb).

🤖 Prompt for AI Agents
In app/controllers/health_controller.rb around lines 3 to 7, the health endpoint
currently inherits ApplicationController before_actions and should skip
non-essential ones; add skip_before_action :set_cache_headers,
:set_default_response_format, raise: false at the top of this controller (above
the show action) so the /health endpoint remains minimal and unaffected by
ApplicationController middleware.

end
2 changes: 1 addition & 1 deletion app/controllers/mobile/translations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Mobile
class TranslationsController < ApplicationController
def index
resources = ResourceContent.includes(:language).translations.one_verse.approved
resources = ResourceContent.includes(:language).translations.one_verse.approved.allowed_to_share
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enforce deterministic ordering to match other endpoints

Add priority ordering so clients get stable results across requests (consistent with v3/v4 controllers).

- resources = ResourceContent.includes(:language).translations.one_verse.approved.allowed_to_share
+ resources = ResourceContent
+               .includes(:language)
+               .translations
+               .one_verse
+               .approved
+               .allowed_to_share
+               .order('priority ASC')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resources = ResourceContent.includes(:language).translations.one_verse.approved.allowed_to_share
resources = ResourceContent
.includes(:language)
.translations
.one_verse
.approved
.allowed_to_share
.order('priority ASC')
🤖 Prompt for AI Agents
In app/controllers/mobile/translations_controller.rb around line 6, the query
lacks deterministic ordering; update the ActiveRecord chain to include the same
priority ordering used by the v3/v4 controllers (e.g. order by priority, then a
stable tie-breaker like id) so responses are stable across requests — append
.order(priority: :desc, id: :asc) (or match the exact direction used in v3/v4)
to the
ResourceContent.includes(...).translations.one_verse.approved.allowed_to_share
chain.


render json: resources, root: :data, each_serializer: Mobile::TranslationSerializer
end
Expand Down
Loading