grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
50
fork

Configure Feed

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

at 38f9ce36eee33901fe8957a696e0c16c308eb8f3 114 lines 3.2 kB view raw
1import { Record as BskyProfile } from "$lexicon/types/app/bsky/actor/profile.ts"; 2import { Record as Profile } from "$lexicon/types/social/grain/actor/profile.ts"; 3import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 4import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 5import { AtUri } from "@atproto/syntax"; 6import { onSignedInArgs } from "@bigmoves/bff"; 7import { 8 differenceInDays, 9 differenceInHours, 10 differenceInMinutes, 11 differenceInWeeks, 12} from "date-fns"; 13import { PUBLIC_URL } from "./env.ts"; 14 15export function formatRelativeTime(date: Date) { 16 const now = new Date(); 17 const weeks = differenceInWeeks(now, date); 18 if (weeks > 0) return `${weeks}w`; 19 20 const days = differenceInDays(now, date); 21 if (days > 0) return `${days}d`; 22 23 const hours = differenceInHours(now, date); 24 if (hours > 0) return `${hours}h`; 25 26 const minutes = differenceInMinutes(now, date); 27 return `${Math.max(1, minutes)}m`; 28} 29 30export function uploadPageLink(selectedGalleryRkey?: string) { 31 return "/upload" + 32 (selectedGalleryRkey ? "?gallery=" + selectedGalleryRkey : ""); 33} 34 35export function profileLink(handleOrDid: string) { 36 return `/profile/${handleOrDid}`; 37} 38 39export function followersLink(handle: string) { 40 return `/profile/${handle}/followers`; 41} 42 43export function followingLink(handle: string) { 44 return `/profile/${handle}/follows`; 45} 46 47export function galleryLink(handle: string, galleryRkey: string) { 48 return `/profile/${handle}/gallery/${galleryRkey}`; 49} 50 51export function photoDialogLink(gallery: GalleryView, image: PhotoView) { 52 return `/dialogs/image?galleryUri=${gallery.uri}&imageCid=${image.cid}`; 53} 54 55export function publicGalleryLink(handle: string, galleryUri: string): string { 56 return `${PUBLIC_URL}${galleryLink(handle, new AtUri(galleryUri).rkey)}`; 57} 58 59export function bskyProfileLink(handle: string) { 60 return `https://bsky.app/profile/${handle}`; 61} 62 63export async function onSignedIn({ actor, ctx }: onSignedInArgs) { 64 const profileResults = ctx.indexService.getRecords<Profile>( 65 "social.grain.actor.profile", 66 { 67 where: [{ field: "did", equals: actor.did }], 68 }, 69 ); 70 71 const profile = profileResults.items[0]; 72 73 if (profile) { 74 console.log("Profile already exists"); 75 return `/profile/${actor.handle}`; 76 } 77 78 // This should only happen once for new users 79 await ctx.backfillCollections({ 80 externalCollections: [ 81 "app.bsky.actor.profile", 82 "app.bsky.graph.follow", 83 "sh.tangled.actor.profile", 84 "sh.tangled.graph.follow", 85 ], 86 repos: [actor.did], 87 }); 88 89 const bskyProfileResults = ctx.indexService.getRecords<BskyProfile>( 90 "app.bsky.actor.profile", 91 { 92 where: [{ field: "did", equals: actor.did }], 93 }, 94 ); 95 96 const bskyProfile = bskyProfileResults.items[0]; 97 98 if (!bskyProfile) { 99 console.error("Failed to get bsky profile"); 100 } 101 102 await ctx.createRecord<Profile>( 103 "social.grain.actor.profile", 104 { 105 displayName: bskyProfile?.displayName ?? undefined, 106 description: bskyProfile?.description ?? undefined, 107 avatar: bskyProfile?.avatar ?? undefined, 108 createdAt: new Date().toISOString(), 109 }, 110 true, 111 ); 112 113 return "/onboard"; 114}