Public: anyone can read this; the crew can change it.

The front door's Made here shows the forge owner's apps and pins, never an app a signed-up stranger made #11906

opened this

Check · Review · Land · Live — No review yet. Next: reviewer, no review yet.

Ready to land

scout/forge:scout/11902-the-front-door-s-made-here-shows-the-for → main 2 commits fork

Review

Sign in to review this change.
3 files changed +52 −6
src/actions/front.ts +15
@@ -247,6 +247,18 @@ export function frontKey(app: App): string {
247247 return String(row?.n);
248248 }
249249
250 /**
251 * The repositories a signed-out visitor is offered by name: the shelf's own
252 * set, featured first, so the jump palette never lists more than the door.
253 */
254 export function doorRepos(app: App): Repo[] {
255 const page = frontPage(app);
256 const seen = new Set<string>();
257 return [...(page.featured === undefined ? [] : [page.featured]), ...page.shelf]
258 .map((e) => e.repo)
259 .filter((r) => !seen.has(r.id) && seen.add(r.id));
260 }
261
250262 /** The front page as a stranger is shown it; `request` is told what the key cost. */
251263 export function frontPage(app: App, now = Date.now(), request?: Request): FrontPage {
252264 const key =
@@ -357,6 +369,9 @@ function buildFrontPage(app: App, now: number): FrontPage {
357369 const ranked = rankFront(
358370 publicApps
359371 .filter((r) => !pinnedIds.has(r.repoId) && !picks.hidden.has(r.repoId))
372 // The door shows the forge's own makers; a stranger's app is found on
373 // /explore or pinned by an admin. Filtered here, before any ranking is kept.
374 .filter((r) => owners.get(repos.get(r.repoId)!.ownerId)?.isAdmin === true)
360375 .map((r) => entry(repos.get(r.repoId)!, false))
361376 .filter(tileable)
362377 .map((e) => ({ ...e, id: e.repo.id, owner: e.repo.ownerName })),
src/web/layout.ts +10 −6
@@ -17,6 +17,7 @@ import { administersAny } from "../core/admin_repos.ts";
1717 import { appsForRepos } from "../core/apps.ts";
1818 import { foundryHref } from "../actions/foundry_door.ts";
1919 import { appsRuntime } from "../apps/runtime.ts";
20 import { doorRepos } from "../actions/front.ts";
2021 import { actionRows, peopleRows, settingsRows } from "./palette_rows.ts";
2122 import { wordClip } from "../text/word_clip.ts";
2223 import { absorbedForNav } from "../actions/features.ts";
@@ -479,12 +480,15 @@ function paletteIndex(ctx: Ctx, extra: PaletteItem[]): Html {
479480 ? listReposByOwner(ctx.app.db, ctx.auth.user.username, ctx.auth.user, wanted)
480481 : [];
481482 const taken = new Set(mine.map((r) => r.id));
482 const repos = [
483 ...mine,
484 ...listReposVisibleTo(ctx.app.db, ctx.auth.user, wanted).filter(
485 (r) => !taken.has(r.id),
486 ),
487 ].slice(0, 30);
483 // A signed-out visitor is offered what the front door shows, no more.
484 const repos = ctx.auth.user
485 ? [
486 ...mine,
487 ...listReposVisibleTo(ctx.app.db, ctx.auth.user, wanted).filter(
488 (r) => !taken.has(r.id),
489 ),
490 ].slice(0, 30)
491 : doorRepos(ctx.app).slice(0, 30);
488492 const apps = appsForRepos(
489493 ctx.app.db,
490494 repos.map((r) => r.id),
test/front.test.ts +27
@@ -393,6 +393,8 @@ describe("the front page", () => {
393393 });
394394
395395 test("ranked, not one owner's: forks lift a stranger's app; stopped, failed and unpictured apps are not shown", async () => {
396 // The door shows apps of the forge's admins unless pinned (#11902); zed is one here.
397 app.db.query(`UPDATE user SET is_admin = 1 WHERE id = ?1`).run(other.id);
396398 const forked = await make(other, "loved");
397399 for (let n = 0; n < 3; n++) {
398400 const f = await createRepo(app, member, {
@@ -783,6 +785,7 @@ describe("the front page", () => {
783785 test("a site admin's weights change the order, within bounds; a value out of bounds is refused (#767)", async () => {
784786 // One app with forks by people, one with ships: which leads depends on
785787 // what counts.
788 app.db.query(`UPDATE user SET is_admin = 1 WHERE id = ?1`).run(other.id);
786789 const forked = await make(other, "weighed-forks");
787790 const forkerA = await createUser(app.db, { origin: "seed", trusted: true, username: "forker-a", email: "[email protected]", password: "a-good-passphrase" });
788791 if (!forkerA.ok) throw new Error(forkerA.error);
@@ -836,4 +839,28 @@ describe("the front page", () => {
836839 expect(reset.status).toBe(303);
837840 expect(frontWeights(app.db)).toEqual(DEFAULT_WEIGHTS);
838841 });
842
843 test("the door shows the owner's apps and pinned ones, never an unpinned stranger's (#11902)", async () => {
844 const mine = await make(admin, "door-mine");
845 const pinned = await make(member, "door-pinned");
846 const loose = await make(member, "door-loose");
847 expect((await put(adminToken, { pinned: ["mia/door-pinned"], hidden: [] })).status).toBe(200);
848 forgetFrontScores(app);
849 const json = (await (await fetch(`${base}/api/v1/front`)).json()) as any;
850 const names: string[] = [json.featured?.repo, ...json.apps.map((a: any) => a.repo)];
851 expect(names).toContain("owner/door-mine");
852 expect(names).toContain("mia/door-pinned");
853 expect(names).not.toContain("mia/door-loose");
854 // The signed-out jump palette offers the same set.
855 const body = await door();
856 const index = JSON.parse(body.match(/id="palette-index">([^<]*)</)![1]!);
857 const offered = index.items.filter((i: any) => i.c === "Repositories").map((i: any) => i.t);
858 expect(offered).toContain("mia/door-pinned");
859 expect(offered).not.toContain("mia/door-loose");
860 expect(offered.sort()).toEqual(names.filter(Boolean).sort());
861 // Still reachable at its own address.
862 expect((await fetch(`${base}/mia/door-loose`)).status).toBe(200);
863 expect((await put(adminToken, { pinned: [], hidden: [] })).status).toBe(200);
864 void mine; void pinned; void loose;
865 });
839866 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs