Skip to content
Merged
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
12 changes: 10 additions & 2 deletions config/nginx.conf.erb
Original file line number Diff line number Diff line change
Expand Up @@ -612,10 +612,18 @@ http {
return 302 https://rubykaigi.org/<%= current_year %>/policies;
}
location = /go/prospectus-ja {
return 302 https://rubykaigi-public.s3.dualstack.ap-northeast-1.amazonaws.com/2026/rubykaigi2026-sponsorship-prospectus-ja.pdf;
set $prospectus_year "<%= current_year %>";
if ($args ~ "^(20[0-9]{2})$") {
set $prospectus_year $1;
}
return 302 https://storage.rubykaigi.org/$prospectus_year/rubykaigi$prospectus_year-sponsorship-prospectus-ja.pdf;
}
location = /go/prospectus-en {
return 302 https://rubykaigi-public.s3.dualstack.ap-northeast-1.amazonaws.com/2026/rubykaigi2026-sponsorship-prospectus-en.pdf;
set $prospectus_year "<%= current_year %>";
if ($args ~ "^(20[0-9]{2})$") {
set $prospectus_year $1;
}
return 302 https://storage.rubykaigi.org/$prospectus_year/rubykaigi$prospectus_year-sponsorship-prospectus-en.pdf;
}
location = /go/discord {
return 302 https://discord.gg/AjSCZzc6YG;
Expand Down
32 changes: 32 additions & 0 deletions spec/rubykaigi_org_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,38 @@
end
end

describe "/go/prospectus-ja" do
let(:res) { http_get("https://rubykaigi.org/go/prospectus-ja") }
it "redirects to latest_year prospectus" do
expect(res.code).to eq("302")
expect(res["location"]).to eq("https://storage.rubykaigi.org/#{latest_year}/rubykaigi#{latest_year}-sponsorship-prospectus-ja.pdf")
end
end

describe "/go/prospectus-ja?2025" do
let(:res) { http_get("https://rubykaigi.org/go/prospectus-ja?2025") }
it "redirects to 2025 prospectus" do
expect(res.code).to eq("302")
expect(res["location"]).to eq("https://storage.rubykaigi.org/2025/rubykaigi2025-sponsorship-prospectus-ja.pdf")
end
end

describe "/go/prospectus-en" do
let(:res) { http_get("https://rubykaigi.org/go/prospectus-en") }
it "redirects to latest_year prospectus" do
expect(res.code).to eq("302")
expect(res["location"]).to eq("https://storage.rubykaigi.org/#{latest_year}/rubykaigi#{latest_year}-sponsorship-prospectus-en.pdf")
end
end

describe "/go/prospectus-en?2025" do
let(:res) { http_get("https://rubykaigi.org/go/prospectus-en?2025") }
it "redirects to 2025 prospectus" do
expect(res.code).to eq("302")
expect(res["location"]).to eq("https://storage.rubykaigi.org/2025/rubykaigi2025-sponsorship-prospectus-en.pdf")
end
end

describe "(https) /2024/" do
let(:res) { http_get("https://rubykaigi.org/2024/") }
it "should render the top page" do
Comment on lines +77 to 87
Copy link

Choose a reason for hiding this comment

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

Bug: The new /go/prospectus endpoint, handled by a CloudFront function, lacks any test coverage. The existing test suite doesn't exercise this production code.
Severity: MEDIUM

Suggested Fix

Add a dedicated test suite for the TypeScript CloudFront function. This could involve using a framework like Jest to unit test the handleProspectusRedirect() logic by mocking the CloudFront event object. This ensures the language detection and query string preservation are tested.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: spec/rubykaigi_org_spec.rb#L50-L87

Potential issue: The new `/go/prospectus` endpoint is implemented via a CloudFront
function (`viewreq.ts`) that performs language detection and redirects users. This
function is deployed to production but is completely untested. The existing Ruby test
suite does not and cannot invoke CloudFront functions, and no separate TypeScript tests
have been added. This creates a risk that bugs in the redirect logic, such as incorrect
handling of query parameters, could go undetected and impact production users.

Did we get this right? 👍 / 👎 to inform future reviews.

Expand Down
2 changes: 1 addition & 1 deletion spec/support/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def http_get(url)
'x-rko-host' => uri.host,
'x-rko-xfp' => uri.scheme,
}
http.get(uri.path, headers)
http.get(uri.request_uri, headers)
end
end
end
52 changes: 48 additions & 4 deletions tf/cf_functions/src/viewreq.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
function handler(
event: AWSCloudFrontFunction.Event,
): AWSCloudFrontFunction.Request {
const { request, context } = event;
// Handle /go/prospectus language redirect for rubykaigi.org
// Returns Response if redirect needed, null otherwise
function handleProspectusRedirect(
request: AWSCloudFrontFunction.Request,
): AWSCloudFrontFunction.Response | null {
const host = request.headers["host"]?.value;
if (host !== "rubykaigi.org" || request.uri !== "/go/prospectus") {
return null;
}

const country = request.headers["cloudfront-viewer-country"]?.value;
const acceptLang = request.headers["accept-language"]?.value || "";

const lang = (country === "JP" || acceptLang.toLowerCase().includes("ja"))
? "ja"
: "en";

// Build query string preserving original
const qs = Object.keys(request.querystring)
.map(k => {
const v = request.querystring[k];
return v.value ? `${k}=${v.value}` : k;
})
.join("&");
const location = `/go/prospectus-${lang}${qs ? "?" + qs : ""}`;

return {
statusCode: 302,
statusDescription: "Found",
headers: {
location: { value: location },
},
};
}

// Set x-rko-host and x-rko-xfp headers for backend routing
function setRkoHeaders(request: AWSCloudFrontFunction.Request): void {
request.headers["x-rko-host"] = request.headers["host"];

const fp = request.headers["cloudfront-forwarded-proto"]?.value;
Expand All @@ -11,6 +43,18 @@ function handler(
} else {
request.headers["x-rko-xfp"] = { value: "http" };
}
}

function handler(
event: AWSCloudFrontFunction.Event,
): AWSCloudFrontFunction.Request | AWSCloudFrontFunction.Response {
const { request } = event;

const redirect = handleProspectusRedirect(request);
if (redirect) {
return redirect;
}

setRkoHeaders(request);
return request;
}