flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/web/app/lib/renamed.server.ts

61 lines2,241 bytesCodeBlame
1import { redirect } from "react-router";
2
3import { isValidNamespace } from "@g1t/contracts";
4
5import type { Viewer } from "@g1t/contracts";
6
7import { identity, repos } from "./services.server";
8
9/**
10 * A workspace that was renamed keeps its old address as a redirect for
11 * `SLUG_HOLD_DAYS`. Call this where a page is about to 404 on its first
12 * path segment: if `slug` is an old name, it throws a 301 to the same
13 * address (path and query) under the current one. Otherwise it returns, and
14 * the caller 404s as before. Only the not-found path pays for the lookup.
15 */
16export async function redirectIfRenamed(request: Request, slug: string): Promise<void> {
17 const old = slug.toLowerCase();
18 // Nothing that could never have been a workspace's name is looked up.
19 if (!isValidNamespace(old)) return;
20 let current: string | null = null;
21 try {
22 current = await identity.resolveSlug(old);
23 } catch {
24 // A lookup that fails leaves the page a 404, never a 500.
25 return;
26 }
27 if (!current || current === old) return;
28 const url = new URL(request.url);
29 const segments = url.pathname.split("/");
30 // segments[0] is the empty string before the leading slash.
31 segments[1] = current;
32 throw redirect(segments.join("/") + url.search, 301);
33}
34
35/**
36 * A repository transferred to another workspace keeps its old address as a
37 * redirect until a repository is made there. Call this where a project page
38 * is about to 404: if `owner/repo` is a path a repository left, and the
39 * viewer may see it where it is now, it throws a 301 to the same page
40 * there. A private repository's new address is never shown to someone who
41 * cannot see it; they get the 404 as before.
42 */
43export async function redirectIfTransferred(
44 request: Request,
45 owner: string,
46 repo: string,
47 viewer: Viewer,
48): Promise<void> {
49 let now: { namespace: string; name: string } | null = null;
50 try {
51 now = await repos.resolvePath({ namespace: owner, name: repo });
52 if (!now || !(await repos.get(now, viewer)).ok) return;
53 } catch {
54 return;
55 }
56 const url = new URL(request.url);
57 const segments = url.pathname.split("/");
58 segments[1] = now.namespace;
59 segments[2] = now.name;
60 throw redirect(segments.join("/") + url.search, 301);
61}