import { Client, CredentialManager } from "@atcute/client"; import { lexiconDoc } from "@atcute/lexicon-doc"; import { ActorIdentifier, is, Nsid, ResourceUri } from "@atcute/lexicons"; import { A, useLocation, useNavigate, useParams } from "@solidjs/router"; import { createResource, createSignal, ErrorBoundary, Show, Suspense } from "solid-js"; import { Backlinks } from "../components/backlinks.jsx"; import { Button } from "../components/button.jsx"; import { RecordEditor } from "../components/create.jsx"; import { CopyMenu, DropdownMenu, MenuProvider, NavMenu } from "../components/dropdown.jsx"; import { JSONValue } from "../components/json.jsx"; import { agent } from "../components/login.jsx"; import { Modal } from "../components/modal.jsx"; import { pds } from "../components/navbar.jsx"; import Tooltip from "../components/tooltip.jsx"; import { setNotif } from "../layout.jsx"; import { didDocCache, resolveLexiconAuthority, resolvePDS } from "../utils/api.js"; import { AtUri, uriTemplates } from "../utils/templates.js"; import { lexicons } from "../utils/types/lexicons.js"; import { verifyRecord } from "../utils/verify.js"; export const RecordView = () => { const location = useLocation(); const navigate = useNavigate(); const params = useParams(); const [openDelete, setOpenDelete] = createSignal(false); const [notice, setNotice] = createSignal(""); const [externalLink, setExternalLink] = createSignal< { label: string; link: string; icon?: string } | undefined >(); const [lexiconUri, setLexiconUri] = createSignal(); const [validRecord, setValidRecord] = createSignal(undefined); const [validSchema, setValidSchema] = createSignal(undefined); const did = params.repo; let rpc: Client; const fetchRecord = async () => { setValidRecord(undefined); setValidSchema(undefined); setLexiconUri(undefined); const pds = await resolvePDS(did); rpc = new Client({ handler: new CredentialManager({ service: pds }) }); const res = await rpc.get("com.atproto.repo.getRecord", { params: { repo: did as ActorIdentifier, collection: params.collection as `${string}.${string}.${string}`, rkey: params.rkey, }, }); if (!res.ok) { setValidRecord(false); setNotice(res.data.error); throw new Error(res.data.error); } setExternalLink(checkUri(res.data.uri, res.data.value)); resolveLexicon(params.collection as Nsid); verify(res.data); return res.data; }; const verify = async (record: { uri: ResourceUri; value: Record; cid?: string | undefined; }) => { try { if (params.collection in lexicons) { if (is(lexicons[params.collection], record.value)) setValidSchema(true); else setValidSchema(false); } else if (params.collection === "com.atproto.lexicon.schema") { try { lexiconDoc.parse(record.value, { mode: "passthrough" }); setValidSchema(true); } catch (e) { console.error(e); setValidSchema(false); } } const { errors } = await verifyRecord({ rpc: rpc, uri: record.uri, cid: record.cid!, record: record.value, didDoc: didDocCache[record.uri.split("/")[2]], }); if (errors.length > 0) { console.warn(errors); setNotice(`Invalid record: ${errors.map((e) => e.message).join("\n")}`); } setValidRecord(errors.length === 0); } catch (err) { console.error(err); setValidRecord(false); } }; const resolveLexicon = async (nsid: Nsid) => { try { const res = await resolveLexiconAuthority(nsid); setLexiconUri(`at://${res}/com.atproto.lexicon.schema/${nsid}`); } catch {} }; const [record, { refetch }] = createResource(fetchRecord); const deleteRecord = async () => { rpc = new Client({ handler: agent()! }); await rpc.post("com.atproto.repo.deleteRecord", { input: { repo: params.repo as ActorIdentifier, collection: params.collection as `${string}.${string}.${string}`, rkey: params.rkey, }, }); setNotif({ show: true, icon: "lucide--trash-2", text: "Record deleted" }); navigate(`/at://${params.repo}/${params.collection}`); }; const checkUri = (uri: string, record: any) => { const uriParts = uri.split("/"); // expected: ["at:", "", "repo", "collection", "rkey"] if (uriParts.length != 5) return undefined; if (uriParts[0] !== "at:" || uriParts[1] !== "") return undefined; const parsedUri: AtUri = { repo: uriParts[2], collection: uriParts[3], rkey: uriParts[4] }; const template = uriTemplates[parsedUri.collection]; if (!template) return undefined; return template(parsedUri, record); }; const RecordTab = (props: { tab: "record" | "backlinks" | "info"; label: string; error?: boolean; }) => ( ); return (
setOpenDelete(false)}>

Delete this record?

{(cid) => } {(externalLink) => ( )}
Error: {err.message}
}> } >

AT URI

{record()?.uri}

CID

{record()?.cid}

Record verification

{notice()}

Schema validation

Lexicon schema

); };