grain.social is a photo sharing platform built on atproto.
at main 3.6 kB view raw
1import { RichText } from "@atproto/api"; 2import { 3 ActorTable, 4 BffContext, 5 LabelerPolicies, 6 RouteHandler, 7} from "@bigmoves/bff"; 8import { ProfilePage, ProfileTabs } from "../components/ProfilePage.tsx"; 9import { 10 getActorGalleries, 11 getActorGalleryFavs, 12 getActorProfileDetailed, 13 getActorProfiles, 14} from "../lib/actor.ts"; 15import { 16 isLabeler as isLabelerFn, 17 moderateGallery, 18 ModerationDecsion, 19} from "../lib/moderation.ts"; 20import { parseFacetedText } from "../lib/rich_text.ts"; 21import { type SocialNetwork } from "../lib/timeline.ts"; 22import { getPageMeta } from "../meta.ts"; 23import type { State } from "../state.ts"; 24import { profileLink } from "../utils.ts"; 25 26export const handler: RouteHandler = async ( 27 req, 28 params, 29 ctx: BffContext<State>, 30) => { 31 const url = new URL(req.url); 32 const tab = url.searchParams.get("tab") as ProfileTabs; 33 const handleOrDid = params.handleOrDid; 34 35 let actor: ActorTable | undefined; 36 if (handleOrDid.includes("did:")) { 37 actor = ctx.indexService.getActor(handleOrDid); 38 } else { 39 actor = ctx.indexService.getActorByHandle(handleOrDid); 40 } 41 42 if (!actor) return ctx.next(); 43 44 const isHxRequest = req.headers.get("hx-request") !== null; 45 const render = isHxRequest ? ctx.html : ctx.render; 46 47 const profile = getActorProfileDetailed(actor.did, ctx); 48 const galleries = getActorGalleries(actor.did, ctx); 49 50 let descriptionFacets: RichText["facets"] = undefined; 51 if (profile?.description) { 52 const resp = parseFacetedText(profile?.description, ctx); 53 descriptionFacets = resp.facets; 54 } 55 56 let labelerDefinitions: LabelerPolicies | undefined = undefined; 57 const isLabeler = await isLabelerFn(actor.did, ctx); 58 if (isLabeler) { 59 const labelerDefs = await ctx.getLabelerDefinitions(); 60 labelerDefinitions = labelerDefs[actor.did] ?? []; 61 } 62 63 const galleryModDecisionsMap = new Map<string, ModerationDecsion>(); 64 for (const gallery of galleries) { 65 if (!gallery.labels || gallery.labels.length === 0) { 66 continue; 67 } 68 const modDecision = await moderateGallery( 69 gallery.labels ?? [], 70 ctx, 71 ); 72 if (!modDecision) { 73 continue; 74 } 75 galleryModDecisionsMap.set(gallery.uri, modDecision); 76 } 77 78 if (!profile) return ctx.next(); 79 80 let actorProfiles: SocialNetwork[] = []; 81 let userProfiles: SocialNetwork[] = []; 82 83 if (ctx.currentUser) { 84 actorProfiles = getActorProfiles(ctx.currentUser.did, ctx); 85 } 86 87 userProfiles = getActorProfiles(actor.did, ctx); 88 89 ctx.state.meta = [ 90 { 91 title: profile.displayName 92 ? `${profile.displayName} (${profile.handle}) — Grain` 93 : `${profile.handle} — Grain`, 94 }, 95 ...getPageMeta(profileLink(actor.did)), 96 ]; 97 98 if (tab === "favs") { 99 const galleryFavs = getActorGalleryFavs(actor.did, ctx); 100 return render( 101 <ProfilePage 102 userProfiles={userProfiles} 103 actorProfiles={actorProfiles} 104 loggedInUserDid={ctx.currentUser?.did} 105 profile={profile} 106 selectedTab="favs" 107 galleries={galleries} 108 galleryFavs={galleryFavs} 109 galleryModDecisionsMap={galleryModDecisionsMap} 110 />, 111 ); 112 } 113 return render( 114 <ProfilePage 115 userProfiles={userProfiles} 116 actorProfiles={actorProfiles} 117 loggedInUserDid={ctx.currentUser?.did} 118 profile={profile} 119 descriptionFacets={descriptionFacets} 120 selectedTab={isLabeler ? "labels" : "galleries"} 121 galleries={galleries} 122 galleryModDecisionsMap={galleryModDecisionsMap} 123 isLabeler={isLabeler} 124 labelerDefinitions={labelerDefinitions} 125 />, 126 ); 127};