Unverified Commit 9ec5aff3 authored by Hong Minhee's avatar Hong Minhee
Browse files

Follow/unfollow

parent cbc8bd56
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ export interface PostListProps extends PostFormProps {
  blog: Blog;
  posts: PostModel[];
  total: bigint;
  followers: bigint;
  nextCursor: string | null;
  domain: string;
}
@@ -17,6 +18,7 @@ export default function PostList(
    blog,
    posts,
    total,
    followers,
    nextCursor,
    domain,
    error,
@@ -32,7 +34,11 @@ export default function PostList(
        <hgroup>
          <h1>{blog.title}</h1>
          <p>
            <strong>@{blog.handle}@{domain}</strong> &middot; {blog.description}
            <strong>@{blog.handle}@{domain}</strong> &middot;{" "}
            <a href="/followers">
              {followers === 1n ? "1 follower" : `${followers} followers`}
            </a>{" "}
            &middot; {blog.description}
          </p>
        </hgroup>
      </header>
+29 −3
Original line number Diff line number Diff line
@@ -12,6 +12,9 @@ import {
import { getBlog } from "../models/blog.ts";
import { openKv } from "../models/kv.ts";
import { countPosts, getPosts } from "../models/post.ts";
import { addFollower } from "fedify/examples/blog/models/follower.ts";
import { Link } from "fedify/vocab/mod.ts";
import { removeFollower } from "fedify/examples/blog/models/follower.ts";

// The `Federation<TContextData>` object is a registry that registers
// federation-related callbacks:
@@ -104,12 +107,29 @@ federation.setInboxListeners("/users/{handle}/inbox")
  .on(Follow, async (ctx, follow) => {
    const blog = await getBlog();
    if (blog == null) return;
    if (follow.id == null) return;
    const actorUri = ctx.getActorUri(blog.handle);
    if (follow.objectId?.href != actorUri.href) {
      return;
    }
    const recipient = await follow.getActor(ctx);
    if (!isActor(recipient)) return;
    if (
      !isActor(recipient) || recipient.id == null ||
      recipient.preferredUsername == null
    ) return;
    const handle =
      `@${recipient.preferredUsername.toString()}@${recipient.id.host}`;
    await addFollower({
      activityId: follow.id.href,
      id: recipient.id.href,
      name: recipient.name?.toString() ?? "",
      url: recipient.url == null
        ? recipient.id.href
        : recipient.url instanceof Link
        ? (recipient.url.href ?? recipient.id).href
        : recipient.url.href,
      handle,
    });
    await ctx.sendActivity(
      { handle: blog.handle },
      recipient,
@@ -119,7 +139,13 @@ federation.setInboxListeners("/users/{handle}/inbox")
      }),
    );
  })
  .on(Undo, (_ctx, undo) => {
    console.log({ undo });
  .on(Undo, async (ctx, undo) => {
    const object = await undo.getObject(ctx);
    if (object instanceof Follow) {
      if (object.id == null) return;
      await removeFollower(object.id.href);
    } else {
      console.debug(undo);
    }
  })
  .onError((e) => console.error(e));
+2 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

import * as $_app from "./routes/_app.tsx";
import * as $_middleware from "./routes/_middleware.ts";
import * as $followers from "./routes/followers.tsx";
import * as $index from "./routes/index.tsx";
import * as $posts_uuid_ from "./routes/posts/[uuid].tsx";
import * as $posts_index from "./routes/posts/index.tsx";
@@ -15,6 +16,7 @@ const manifest = {
  routes: {
    "./routes/_app.tsx": $_app,
    "./routes/_middleware.ts": $_middleware,
    "./routes/followers.tsx": $followers,
    "./routes/index.tsx": $index,
    "./routes/posts/[uuid].tsx": $posts_uuid_,
    "./routes/posts/index.tsx": $posts_index,
+58 −0
Original line number Diff line number Diff line
import { openKv } from "./kv.ts";

export interface Follower {
  activityId: string;
  id: string;
  name: string;
  url: string;
  handle: string;
}

export async function addFollower(follower: Follower): Promise<Follower> {
  const kv = await openKv();
  const followers = await kv.get<bigint>(["followers"]);
  await kv.atomic()
    .check(followers)
    .set(["follower", follower.activityId], follower)
    .set(["followers"], (followers.value ?? 0n) + 1n)
    .commit();
  return follower;
}

export async function removeFollower(activityId: string): Promise<void> {
  const kv = await openKv();
  const follower = await kv.get<Follower>(["follower", activityId]);
  const followers = await kv.get<bigint>(["followers"]);
  if (
    follower == null || follower.value == null || followers == null ||
    followers.value == null
  ) return;
  await kv.atomic()
    .check(follower)
    .check(followers)
    .delete(["follower", activityId])
    .set(["followers"], followers.value - 1n)
    .commit();
}

export async function getFollowers(
  limit = 5,
  cursor?: string,
): Promise<{ followers: Follower[]; nextCursor: string | null }> {
  const kv = await openKv();
  const it = kv.list<Follower>({ prefix: ["follower"] }, {
    limit,
    cursor,
  });
  const followers: Follower[] = [];
  for await (const entry of it) {
    followers.push(entry.value);
  }
  return { followers, nextCursor: followers.length < limit ? null : it.cursor };
}

export async function countFollowers(): Promise<bigint> {
  const kv = await openKv();
  const record = await kv.get(["followers"]);
  return (record?.value as bigint | null) ?? 0n;
}
+1 −2
Original line number Diff line number Diff line
@@ -18,10 +18,9 @@ export async function addPost(post: Omit<Post, "uuid"> | Post): Promise<Post> {
    uuid = post.uuid;
  }
  const newPost = { uuid, ...post, published: post.published.toString() };
  const one = new Deno.KvU64(1n);
  await kv.atomic()
    .set(["post", uuid], newPost)
    .sum(["count"], one.value)
    .sum(["count"], 1n)
    .commit();
  return { uuid, ...post };
}
Loading