Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 3 additions & 26 deletions app/views/pages/merchandise.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<%# One product. The card below was pasted three times -- same image, same
.shirt_1 keys, same link -- so the page advertised a single T-shirt as
though it were a catalogue. A test below holds it to one. %>
<article class="merchandise">
<header>
<h2><%= t('.title') %></h2>
Expand All @@ -17,31 +20,5 @@
</div>
</div>
</div>
<div class="col">
<div class="card">
<%= image_tag 'OpenIPC-t-shirt-flo-blue.webp',
alt: "Image: Model in an OpenIPC T-Shirt",
title: "OpenIPC Official T-shirt in Florida Blue and White",
class: 'card-image' %>
<div class="card-body">
<h2 class="card-title"><%= t('.shirt_1.title') %></h2>
<p><%= t('.shirt_1.description') %></p>
<a href="https://weededwords.com/product/openipc-t-shirt/"><%= t('.order_from_weededwords') %></a>
</div>
</div>
</div>
<div class="col">
<div class="card">
<%= image_tag 'OpenIPC-t-shirt-flo-blue.webp',
alt: "Image: Model in an OpenIPC T-Shirt",
title: "OpenIPC Official T-shirt in Florida Blue and White",
class: 'card-image' %>
<div class="card-body">
<h2 class="card-title"><%= t('.shirt_1.title') %></h2>
<p><%= t('.shirt_1.description') %></p>
<a href="https://weededwords.com/product/openipc-t-shirt/"><%= t('.order_from_weededwords') %></a>
</div>
</div>
</div>
</div>
</article>
5 changes: 5 additions & 0 deletions app/views/snapshots/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
<header>
<h2><%= t('title.openwall') %></h2>
<p><%=t('.subtitle') %></p>
<%# The wall is in the navigation and the footer now, so it gets visitors who
have not seen it before and cannot tell from a grid of stills whether
these were collected or volunteered. Two days is the retention
PurgeImagesJob::RETENTION actually enforces. %>
<p class="text-body-secondary small" style="max-width: 70ch"><i class="bi bi-camera-video me-1"></i><%= t('.intro_html') %></p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Unverified upload consent claim 🐞 Bug ≡ Correctness

The new intro presents every wall image as voluntarily uploaded by an OpenIPC camera, but
SnapshotsController#create accepts unauthenticated uploads and neither requires OpenIPC firmware
nor records/verifies an Open Wall opt-in. Arbitrary clients can therefore publish images that the
page falsely represents as owner-volunteered camera submissions.
Agent Prompt
## Issue description
The Open Wall intro asserts consent and OpenIPC-camera provenance that the upload endpoint does not verify. Either add enforceable uploader authentication/opt-in provenance or revise every locale to describe only what the system can guarantee.

## Issue Context
`SnapshotsController#create` is CSRF-exempt, permits firmware as optional metadata, and saves snapshots without authentication or a consent/opt-in field. Model validations only cover the image, MAC address, blacklist, and upload interval.

## Fix Focus Areas
- app/views/snapshots/index.html.erb[12-16]
- config/locales/en.yml[191-191]
- config/locales/ru.yml[193-193]
- config/locales/zh.yml[191-191]
- app/controllers/snapshots_controller.rb[15-24]
- app/models/snapshot.rb[61-64]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +14 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Two-day deletion overpromised 🐞 Bug ≡ Correctness

The intro says images are deleted after two days, but the two-day constant is only a cutoff for a
once-nightly purge, so snapshots can remain published for nearly three days even when cron succeeds
and longer when it fails. This makes the new user-facing retention assurance stricter than the
behavior actually implemented.
Agent Prompt
## Issue description
The copy promises deletion after two days although cleanup runs only once nightly after records cross the two-day threshold. Revise all translations to communicate the real upper-bound/best-effort behavior, or schedule per-snapshot deletion so the stated retention is enforced.

## Issue Context
`PurgeImagesJob::RETENTION` selects records older than two days at execution time. The deployment cron invokes that job only at 01:30 daily, leaving an additional delay of up to almost 24 hours.

## Fix Focus Areas
- app/views/snapshots/index.html.erb[12-16]
- config/locales/en.yml[191-191]
- config/locales/ru.yml[193-193]
- config/locales/zh.yml[191-191]
- app/jobs/purge_images_job.rb[11-18]
- deploy/purge-snapshots.sh[3-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

</header>

<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3 mb-4">
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ en:
icon:
snapshot_alt: 'Image: Snapshot'
index:
intro_html: Every image here was uploaded voluntarily by a camera running OpenIPC firmware, and is deleted automatically after two days. Want to join? Enable the Open Wall option in your camera's web interface.
no_signal: No signal
snapshot_alt: 'Image: Snapshot'
stay_tuned: stay tuned
Expand Down
1 change: 1 addition & 0 deletions config/locales/ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ ru:
icon:
snapshot_alt: 'Изображение: снимок'
index:
intro_html: Каждый снимок здесь добровольно загружен камерой с прошивкой OpenIPC и удаляется автоматически через два дня. Хотите присоединиться? Включите опцию Open Wall в веб-интерфейсе своей камеры.
no_signal: Нет сигнала
snapshot_alt: 'Изображение: снимок'
stay_tuned: Следите за обновлениями
Expand Down
1 change: 1 addition & 0 deletions config/locales/zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ zh:
icon:
snapshot_alt: 图片:快照
index:
intro_html: 这里的每一张图片都是由运行 OpenIPC 固件的摄像机自愿上传的,并会在两天后自动删除。想加入吗?在你的摄像机 Web 界面中开启 Open Wall 选项即可。
no_signal: 没信号
snapshot_alt: 图片:快照
stay_tuned: 敬请关注
Expand Down
76 changes: 76 additions & 0 deletions test/controllers/legacy_pages_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

require 'test_helper'

# The pages that predate the relaunch and survived it.
#
# They are the ones nobody thinks about, and they render through the layout,
# navbar and footer that the relaunch rewrote -- so a mistake in any of those
# shows up here first, or not at all. Six of these were served by the app and
# rendered by no test at all before this file.
class LegacyPagesTest < ActionDispatch::IntegrationTest
# path => a string the page must contain, so the test fails on an empty or
# wrong render rather than merely on a non-200.
PAGES = {
'/green_life' => 'green_life',
'/merchandise' => 'merchandise',
'/our-team' => 'our_team',
'/stages-of-firmware-development' => 'stages_of_firmware_development',
'/utilities' => 'utilities',
'/web-interface' => 'web_interface',
'/majestic-endpoints' => 'majestic_endpoints',
'/tools/firmware-partitions-calculation' => 'firmware_partitions_calculation',
'/tools/high-resolution-timer' => 'high_resolution_timer',
'/tools/qr-code-generator' => 'qr_code_generator'
}.freeze

PAGES.each do |path, key|
test "#{path} renders" do
get path

assert_response :success
assert_includes response.body, I18n.t("pages.#{key}.title"),
"#{path} rendered without its own title"
assert_no_match(/translation missing/i, response.body)
end
end

# These keep the layout's wrapper, unlike the relaunched pages. Asserting it
# here pins the other side of the content_for(:fullwidth) switch against the
# pages that actually depend on it.
test 'they render inside the layout wrapper' do
PAGES.each_key do |path|
get path

assert_not_empty css_select('main > div.container.mb-4'), "#{path} lost the wrapper"
end
end

# Every one of them renders the rewritten navbar and footer.
test 'they carry the navigation and the footer' do
PAGES.each_key do |path|
get path

assert_not_empty css_select('nav.navbar a.navbar-brand'), "#{path} has no navbar"
assert_not_empty css_select('footer.site-footer'), "#{path} has no footer"
end
end

# The page advertised a single T-shirt three times: the same image, the same
# .shirt_1 keys and the same link, pasted into three cards.
test 'merchandise lists each product once' do
get '/merchandise'

assert_equal 1, response.body.scan('weededwords.com').size
end

# The wall says what it is. It is linked from the navigation and the footer
# now, so it gets visitors who cannot otherwise tell whether the images were
# collected or volunteered.
test 'the Open Wall explains itself' do
get '/open-wall'

assert_response :success
assert_includes response.body, I18n.t('snapshots.index.intro_html')
end
end
12 changes: 12 additions & 0 deletions test/controllers/relaunch_pages_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ class RelaunchPagesTest < ActionDispatch::IntegrationTest
end
end

# The link test above proves that whatever the homepage links resolves. It
# cannot notice a link that stopped being rendered at all -- a pillar card
# losing its href, or a section being dropped in a refactor -- so the
# destinations the homepage is *for* are named here explicitly.
test 'the homepage links every place it is supposed to send people' do
get '/'

%w[/get-started /low-latency /ecosystem /business /supported-hardware /open-wall /donate].each do |path|
assert_not_empty css_select(%(a[href="#{path}"])), "the homepage no longer links #{path}"
end
end

# The integrator wall is territory-specific: these companies serve Russia and
# were explicitly not to be shown to everyone.
test 'Russian integrators appear for ru and for nobody else' do
Expand Down
Loading