feat: add product-aware deletion emails and subdomain deletion/downloads routes
- Extend DeletionForm with product discriminator and configurable cooldown cookie - Extract env-free deletion email helpers into deletion-email.ts with Lineage/Nessa branding - Make misc.sendDeletionRequestEmail mutation product-aware (subject, html content, cookie) - Replace legacy /deletion/life-and-lineage page with 308 redirect to lineage subdomain - Add Lineage and Nessa subdomain deletion route modules with shared content components - Add Lineage downloads route modules with shared content - Update privacy policy deletion link to lineage subdomain
This commit is contained in:
46
src/routes/deletion/life-and-lineage.test.ts
Normal file
46
src/routes/deletion/life-and-lineage.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Regression test for the legacy `/deletion/life-and-lineage` route (task 11).
|
||||
*
|
||||
* The route was converted from a rendered page into a 308 permanent redirect
|
||||
* to `lineage.freno.me/deletion`. Because the route file is a SolidStart
|
||||
* server module, this is a STATIC SOURCE AUDIT (same pattern as the
|
||||
* `misc.test.ts` regression tests) asserting:
|
||||
* - The route exports a `GET` handler (API-route redirect, not a page).
|
||||
* - The response status is 308 (permanent).
|
||||
* - The `Location` header derives from the centralized
|
||||
* `LEGACY_DELETION_REDIRECT_TARGET` constant (not a hardcoded literal), so
|
||||
* the unit test in `deletion-content.test.ts` is the single source of
|
||||
* truth for the destination.
|
||||
* - No page component / DeletionForm import remains (the form moved to
|
||||
* `src/routes/lineage/deletion.tsx`).
|
||||
*/
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const SOURCE = readFileSync(
|
||||
join(import.meta.dir, "life-and-lineage.tsx"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
describe("Legacy /deletion/life-and-lineage — redirect (task 11)", () => {
|
||||
it("is a GET handler (API-route redirect, not a rendered page)", () => {
|
||||
expect(SOURCE).toContain("export function GET()");
|
||||
expect(SOURCE).not.toContain("export default function");
|
||||
});
|
||||
|
||||
it("responds with a 308 permanent redirect", () => {
|
||||
expect(SOURCE).toContain("status: 308");
|
||||
});
|
||||
|
||||
it("derives the Location from the centralized constant", () => {
|
||||
expect(SOURCE).toContain("LEGACY_DELETION_REDIRECT_TARGET");
|
||||
// The constant is imported from the lineage deletion-content module.
|
||||
expect(SOURCE).toContain("~/routes/lineage/deletion-content");
|
||||
});
|
||||
|
||||
it("no longer ships a page component / DeletionForm", () => {
|
||||
expect(SOURCE).not.toContain("DeletionForm");
|
||||
expect(SOURCE).not.toContain("PageHead");
|
||||
});
|
||||
});
|
||||
@@ -1,31 +1,29 @@
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import DeletionForm from "~/components/DeletionForm";
|
||||
/**
|
||||
* Legacy Life and Lineage account-deletion route — now a 308 permanent
|
||||
* redirect to the Lineage subdomain (task 11).
|
||||
*
|
||||
* The deletion form has been migrated to `src/routes/lineage/deletion.tsx`
|
||||
* served at `lineage.freno.me/deletion` (vercel.json host rewrites map the
|
||||
* subdomain to the `/lineage/*` internal prefix). Keeping this route as a
|
||||
* permanent (308) server-side redirect — rather than a client `<Navigate>` —
|
||||
* preserves SEO equity and gives installed / linked / support-emailed URLs a
|
||||
* stable resolution path to the new home.
|
||||
*
|
||||
* Implemented as a SolidStart API route (`GET` handler returning a Response)
|
||||
* so the redirect happens before any rendering; the route no longer ships a
|
||||
* page component. The redirect target is centralized in
|
||||
* `~/routes/lineage/deletion-content.ts` (`LEGACY_DELETION_REDIRECT_TARGET`)
|
||||
* so the unit test can assert the destination without importing this server
|
||||
* module.
|
||||
*/
|
||||
import { LEGACY_DELETION_REDIRECT_TARGET } from "~/routes/lineage/deletion-content";
|
||||
|
||||
export default function LifeAndLinageDeletionForm() {
|
||||
return (
|
||||
<>
|
||||
<PageHead
|
||||
title="Account Deletion - Life and Lineage"
|
||||
description="Request account deletion for Life and Lineage. Remove all your data from our system with a 24-hour grace period."
|
||||
/>
|
||||
<div class="pt-20">
|
||||
<div class="mx-auto p-4 md:p-6 lg:p-12">
|
||||
<div class="text-text w-full justify-center">
|
||||
<div class="text-xl">
|
||||
<em>What will happen</em>:
|
||||
</div>
|
||||
Once you send, if a match to the email provided is found in our
|
||||
system, a 24hr grace period is started where you can request a
|
||||
cancellation of the account deletion. Once the grace period ends,
|
||||
the account's entry in our central database will be completely
|
||||
removed, and your individual database storing your remote saves will
|
||||
also be deleted. No data related to the account is retained in any
|
||||
way.
|
||||
</div>
|
||||
|
||||
<DeletionForm />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
export function GET() {
|
||||
return new Response(null, {
|
||||
status: 308,
|
||||
headers: {
|
||||
Location: LEGACY_DELETION_REDIRECT_TARGET,
|
||||
"Cache-Control": "public, max-age=0, must-revalidate"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user