This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

web: serve raw repo files

Signed-off-by: dawn <dawn@tangled.org>

author
dawn
date (Jul 25, 2026, 9:13 PM +0300) commit 397b66b2 parent be0f84fa change-id txwkrmqr
+57 -6
+1 -1
api/tangled/repoblob.go
··· 72 72 // path: Path to the file within the repository 73 73 // raw: Return raw file content instead of JSON response 74 74 // ref: Git reference (branch, tag, or commit SHA) 75 - // repo: DID of the repository 75 + // repo: AT-URI of the sh.tangled.repo record 76 76 func RepoBlob(ctx context.Context, c util.LexClient, path string, raw bool, ref string, repo string) (*RepoBlob_Output, error) { 77 77 var out RepoBlob_Output 78 78
+2 -2
lexicons/repo/blob.json
··· 14 14 "properties": { 15 15 "repo": { 16 16 "type": "string", 17 - "format": "did", 18 - "description": "DID of the repository" 17 + "format": "at-uri", 18 + "description": "AT-URI of the sh.tangled.repo record" 19 19 }, 20 20 "ref": { 21 21 "type": "string",
+10 -1
web/src/lib/api/knot.ts
··· 1 1 import type { BobbinContext, QueryValue, XrpcRequestInit } from "./client"; 2 - import { jsonGet, rawGet } from "./_request"; 2 + import { buildUrl, jsonGet, rawGet } from "./_request"; 3 3 import type * as Archive from "./lexicons/types/sh/tangled/repo/archive"; 4 4 import type * as Blob_ from "./lexicons/types/sh/tangled/repo/blob"; 5 5 import type * as Branch from "./lexicons/types/sh/tangled/repo/branch"; ··· 92 92 93 93 export const archive = (ctx: BobbinContext, params: Archive.$params, init?: XrpcRequestInit) => 94 94 rawGet(ctx, "sh.tangled.repo.archive", asParams(params), init); 95 + 96 + // the knot picks the content type and only serves images, video and text, so a 97 + // reader can go straight here instead of us proxying it 98 + export const blobRawUrl = ( 99 + serviceUrl: string, 100 + // not `Blob_.$params`, the record wrappers hand back plain strings and 101 + // nothing here would satisfy the generated at-uri brand 102 + params: { repo: string; ref: string; path: string } 103 + ): string => buildUrl(serviceUrl, "sh.tangled.repo.blob", { ...params, raw: true }).toString();
+2 -2
web/src/lib/api/lexicons/types/sh/tangled/repo/blob.ts
··· 38 38 */ 39 39 ref: /*#__PURE__*/ v.string(), 40 40 /** 41 - * DID of the repository 41 + * AT-URI of the sh.tangled.repo record 42 42 */ 43 - repo: /*#__PURE__*/ v.didString(), 43 + repo: /*#__PURE__*/ v.resourceUriString(), 44 44 }), 45 45 output: { 46 46 type: "lex",
+42
web/src/routes/[handle]/[repo]/raw/[ref]/[...path]/+server.ts
··· 1 + import { error } from "@sveltejs/kit"; 2 + import { createBobbinClient } from "$lib/api/client"; 3 + import { resolveMiniDoc } from "$lib/api/identity"; 4 + import { blobRawUrl } from "$lib/api/knot"; 5 + import { toHttpError } from "$lib/api/load"; 6 + import { resolveRepoByName } from "$lib/api/repo"; 7 + import { getConfig } from "$lib/server/config"; 8 + import type { RequestHandler } from "./$types"; 9 + 10 + // redirecting instead of proxying keeps repo content off our own origin, same 11 + // as camo and avatar 12 + export const GET: RequestHandler = async (event) => { 13 + const identifier = decodeURIComponent(event.params.handle); 14 + const name = decodeURIComponent(event.params.repo); 15 + const { ref, path } = event.params; 16 + 17 + // rejects bare words so unrelated paths 404 instead of resolving as actors 18 + if (!identifier.startsWith("did:") && !identifier.includes(".")) { 19 + error(404, "Not found"); 20 + } 21 + if (path === "") error(404, "Not found"); 22 + 23 + const { bobbinUrl } = getConfig(); 24 + const ctx = createBobbinClient({ serviceUrl: bobbinUrl, fetch: event.fetch }); 25 + 26 + const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 27 + toHttpError(cause, "Could not resolve user") 28 + ); 29 + const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) => 30 + toHttpError(cause, "Could not load repository") 31 + ); 32 + if (!view) error(404, `${doc.handle}/${name} does not exist`); 33 + 34 + // a branch points at a new commit after every push, so this cannot cache 35 + return new Response(null, { 36 + status: 302, 37 + headers: { 38 + location: blobRawUrl(bobbinUrl, { repo: view.uri, ref, path }), 39 + "cache-control": "public, no-cache" 40 + } 41 + }); 42 + };