~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
131 lines · 4.2 KB Raw History
import { defineQuery } from "$hatk";
import { views } from "$hatk";
import type { GrainActorProfile, Story, Label } from "$hatk";
import { lookupCrossPosts } from "../hydrate/galleries.ts";
import { lookupHandles } from "../helpers/lookupHandles.ts";
import { resolveAtUri } from "../helpers/resolveHandle.ts";
import { lookupViewedStories, storyViewerState } from "../helpers/storyViews.ts";

export default defineQuery("social.grain.unspecced.getStory", async (ctx) => {
  const { db, ok } = ctx;
  let storyUri = ctx.params.story;
  if (!storyUri) return ok({});

  // Resolve handle in AT URI if needed
  const resolved = await resolveAtUri(db, storyUri);
  if (resolved) storyUri = resolved;

  const rows = (await db.query(
    `SELECT s.uri, s.cid, s.did, s.media, s.aspect_ratio, s.location, s.address, s.created_at
     FROM "social.grain.story" s
     LEFT JOIN _repos r ON r.did = s.did
     WHERE s.uri = $1
       AND (r.status IS NULL OR r.status NOT IN ('takendown', 'deactivated', 'deleted'))`,
    [storyUri],
  )) as {
    uri: string;
    cid: string;
    did: string;
    media: string;
    aspect_ratio: string;
    location: string | null;
    address: string | null;
    created_at: string;
  }[];

  const row = rows[0];
  if (!row) return ok({});

  // Resolve author profile
  const [profiles, handleMap] = await Promise.all([
    ctx.lookup<GrainActorProfile>("social.grain.actor.profile", "did", [row.did]),
    lookupHandles(db, [row.did]),
  ]);
  const author = profiles.get(row.did);
  const profileView = author
    ? views.grainActorDefsProfileView({
        cid: author.cid,
        did: author.did,
        handle: author.handle ?? handleMap.get(author.did) ?? author.did,
        displayName: author.value.displayName,
        avatar: ctx.blobUrl(author.did, author.value.avatar) ?? undefined,
      })
    : views.grainActorDefsProfileView({
        cid: "",
        did: row.did,
        handle: handleMap.get(row.did) ?? row.did,
      });

  let blobRef: any;
  try {
    blobRef = typeof row.media === "string" ? JSON.parse(row.media) : row.media;
  } catch {
    blobRef = row.media;
  }

  let aspectRatio: { width: number; height: number };
  try {
    aspectRatio =
      typeof row.aspect_ratio === "string" ? JSON.parse(row.aspect_ratio) : row.aspect_ratio;
  } catch {
    aspectRatio = { width: 4, height: 3 };
  }

  let location: Story["location"] | null = null;
  if (row.location) {
    try {
      location = typeof row.location === "string" ? JSON.parse(row.location) : row.location;
    } catch {
      location = null;
    }
  }

  let address: Story["address"] | null = null;
  if (row.address) {
    try {
      address = typeof row.address === "string" ? JSON.parse(row.address) : row.address;
    } catch {
      address = null;
    }
  }

  // Labels: merge external + self-labels
  const externalLabels = (await ctx.labels([row.uri])) as Map<string, Label[]>;
  const selfLabelRows = (await db.query(
    `SELECT parent_uri, val FROM "social.grain.story__labels_self_labels" WHERE parent_uri = $1`,
    [row.uri],
  )) as { parent_uri: string; val: string }[];
  const labels = externalLabels.get(row.uri) ?? [];
  for (const sl of selfLabelRows) {
    labels.push({ src: row.did, uri: row.uri, val: sl.val, cts: new Date().toISOString() });
  }

  // Cross-post lookup, and whether the viewer has already watched it
  const [crossPostMap, viewed] = await Promise.all([
    lookupCrossPosts(db, [row], "story"),
    lookupViewedStories(db, ctx.viewer?.did, [row.uri]),
  ]);
  const crossPostUrl = crossPostMap.get(row.uri);
  const crossPost = crossPostUrl ? { url: crossPostUrl } : undefined;

  const story = views.storyView({
    uri: row.uri,
    cid: row.cid,
    creator: profileView,
    thumb: ctx.blobUrl(row.did, blobRef, "feed_thumbnail") ?? "",
    fullsize: ctx.blobUrl(row.did, blobRef, "feed_fullsize") ?? "",
    aspectRatio,
    ...(location
      ? {
          location: { name: location.name, value: location.value },
          ...(address ? { address } : {}),
        }
      : {}),
    ...(crossPost ? { crossPost } : {}),
    ...(labels.length > 0 ? { labels } : {}),
    createdAt: row.created_at,
    ...storyViewerState(undefined, viewed.has(row.uri)),
  });

  return ok({ story });
});