grain.social is a photo sharing platform built on atproto.
at main 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}