diff --git a/CHANGES.md b/CHANGES.md
index 2891105..95088c6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,6 +27,9 @@ To be released.
- Added support for RFC 8414 for OAuth Authorization Server metadata endpoint.
[[#47] by Emelia Smith]
+ - On creating a new account, the user now can choose to follow the official
+ Hollo account.
+
- Added a favicon.
- Added `LISTEN_PORT` and `ALLOW_PRIVATE_ADDRESS` environment variables.
diff --git a/src/api/v1/accounts.ts b/src/api/v1/accounts.ts
index 32a5e18..e97bf33 100644
--- a/src/api/v1/accounts.ts
+++ b/src/api/v1/accounts.ts
@@ -1,13 +1,5 @@
import { PutObjectCommand } from "@aws-sdk/client-s3";
-import {
- Block,
- Follow,
- type Recipient,
- Reject,
- Undo,
- isActor,
- lookupObject,
-} from "@fedify/fedify";
+import { Block, Undo, isActor, lookupObject } from "@fedify/fedify";
import * as vocab from "@fedify/fedify/vocab";
import { zValidator } from "@hono/zod-validator";
import {
@@ -19,7 +11,6 @@ import {
gte,
ilike,
inArray,
- isNotNull,
isNull,
lt,
lte,
@@ -38,13 +29,19 @@ import {
import { serializeList } from "../../entities/list";
import { getPostRelations, serializePost } from "../../entities/status";
import { federation } from "../../federation";
-import { persistAccount, persistAccountPosts } from "../../federation/account";
+import {
+ REMOTE_ACTOR_FETCH_POSTS,
+ followAccount,
+ persistAccount,
+ persistAccountPosts,
+ removeFollower,
+ unfollowAccount,
+} from "../../federation/account";
import { type Variables, scopeRequired, tokenRequired } from "../../oauth";
import { S3_BUCKET, S3_URL_BASE, s3 } from "../../s3";
import {
type Account,
type AccountOwner,
- type NewFollow,
type NewMute,
accountOwners,
accounts,
@@ -530,13 +527,9 @@ app.get(
.select({ cnt: count() })
.from(posts)
.where(eq(posts.accountId, account.id));
- const fetchPosts = Number.parseInt(
- // biome-ignore lint/complexity/useLiteralKeys: tsc rants about this (TS4111)
- process.env["REMOTE_ACTOR_FETCH_POSTS"] ?? "10",
- );
- if (cnt < fetchPosts) {
+ if (cnt < REMOTE_ACTOR_FETCH_POSTS) {
const fedCtx = federation.createContext(c.req.raw, undefined);
- await persistAccountPosts(db, account, fetchPosts, {
+ await persistAccountPosts(db, account, REMOTE_ACTOR_FETCH_POSTS, {
documentLoader: await fedCtx.getDocumentLoader({
username: tokenOwner.handle,
}),
@@ -682,46 +675,19 @@ app.post(
const id = c.req.param("id");
const following = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
- with: { owner: true, mutes: { where: eq(mutes.accountId, owner.id) } },
+ with: { owner: true },
});
if (following == null) return c.json({ error: "Record not found" }, 404);
- const result = await db
- .insert(follows)
- .values({
- iri: new URL(`#follows/${crypto.randomUUID()}`, owner.account.iri).href,
- followingId: following.id,
- followerId: owner.id,
- shares: true,
- notify: false,
- languages: null,
- approved:
- following.owner == null || following.protected ? null : new Date(),
- } satisfies NewFollow)
- .onConflictDoNothing()
- .returning();
- // TODO: respond with 403 if the following blocks the follower
- if (result.length < 1) {
+ const fedCtx = federation.createContext(c.req.raw, undefined);
+ const follow = await followAccount(
+ db,
+ fedCtx,
+ { ...owner.account, owner },
+ following,
+ );
+ if (follow == null) {
return c.json({ error: "The action is not allowed" }, 403);
}
- const follow = result[0];
- if (following.owner == null) {
- const fedCtx = federation.createContext(c.req.raw, undefined);
- await fedCtx.sendActivity(
- { username: owner.handle },
- [
- {
- id: new URL(following.iri),
- inboxId: new URL(following.inboxUrl),
- },
- ],
- new vocab.Follow({
- id: new URL(follow.iri),
- actor: new URL(owner.account.iri),
- object: new URL(following.iri),
- }),
- { excludeBaseUris: [new URL(fedCtx.url)] },
- );
- }
const account = await db.query.accounts.findFirst({
where: eq(accounts.id, following.id),
with: {
@@ -760,58 +726,13 @@ app.post(
);
}
const id = c.req.param("id");
- const result = await db
- .delete(follows)
- .where(and(eq(follows.followingId, id), eq(follows.followerId, owner.id)))
- .returning({ iri: follows.iri });
-
- if (result.length > 0) {
- const fedCtx = federation.createContext(c.req.raw, undefined);
- const following = await db.query.accounts.findFirst({
- where: eq(accounts.id, id),
- with: {
- owner: true,
- mutes: {
- where: eq(mutes.accountId, owner.id),
- },
- },
- });
- if (following != null && following.owner == null) {
- await fedCtx.sendActivity(
- { username: owner.handle },
- [
- {
- id: new URL(following.iri),
- inboxId: new URL(following.inboxUrl),
- },
- ],
- new Undo({
- id: new URL(`#unfollows/${crypto.randomUUID()}`, owner.account.iri),
- actor: new URL(owner.account.iri),
- object: new Follow({
- id: new URL(result[0].iri),
- actor: new URL(owner.account.iri),
- object: new URL(following.iri),
- }),
- }),
- { excludeBaseUris: [new URL(fedCtx.url)] },
- );
- }
- await db
- .update(accounts)
- .set({
- followingCount: sql`${db
- .select({ cnt: count() })
- .from(follows)
- .where(
- and(
- eq(follows.followerId, owner.id),
- isNotNull(follows.approved),
- ),
- )}`,
- })
- .where(eq(accounts.id, owner.id));
- }
+ const following = await db.query.accounts.findFirst({
+ where: eq(accounts.id, id),
+ with: { owner: true },
+ });
+ if (following == null) return c.json({ error: "Record not found" }, 404);
+ const fedCtx = federation.createContext(c.req.raw, undefined);
+ await unfollowAccount(db, fedCtx, { ...owner.account, owner }, following);
const account = await db.query.accounts.findFirst({
where: eq(accounts.id, id),
with: {
@@ -1098,57 +1019,11 @@ app.post(
});
if (acct.owner == null) {
const fedCtx = federation.createContext(c.req.raw, undefined);
- const recipient: Recipient = {
- id: new URL(acct.iri),
- inboxId: new URL(acct.inboxUrl),
- };
- const following = await db
- .delete(follows)
- .where(
- and(eq(follows.followingId, id), eq(follows.followerId, owner.id)),
- )
- .returning();
- if (following.length > 0) {
- await fedCtx.sendActivity(
- { username: owner.handle },
- recipient,
- new Undo({
- id: new URL(`#unfollows/${crypto.randomUUID()}`, owner.account.iri),
- actor: new URL(owner.account.iri),
- object: new Follow({
- id: new URL(following[0].iri),
- actor: new URL(owner.account.iri),
- object: new URL(acct.iri),
- }),
- }),
- { excludeBaseUris: [new URL(fedCtx.url)] },
- );
- }
- const follower = await db
- .delete(follows)
- .where(
- and(eq(follows.followerId, id), eq(follows.followingId, owner.id)),
- )
- .returning();
- if (follower.length > 0) {
- await fedCtx.sendActivity(
- { username: owner.handle },
- recipient,
- new Reject({
- id: new URL(`#reject/${crypto.randomUUID()}`, owner.account.iri),
- actor: new URL(owner.account.iri),
- object: new Follow({
- id: new URL(follower[0].iri),
- actor: new URL(acct.iri),
- object: new URL(owner.account.iri),
- }),
- }),
- { excludeBaseUris: [new URL(fedCtx.url)] },
- );
- }
+ await unfollowAccount(db, fedCtx, { ...owner.account, owner }, acct);
+ await removeFollower(db, fedCtx, { ...owner.account, owner }, acct);
await fedCtx.sendActivity(
{ username: owner.handle },
- recipient,
+ { id: new URL(acct.iri), inboxId: new URL(acct.inboxUrl) },
new Block({
id: new URL(`#block/${acct.id}`, owner.account.iri),
actor: new URL(owner.account.iri),
diff --git a/src/components/AccountForm.tsx b/src/components/AccountForm.tsx
index c17218b..a4b34ac 100644
--- a/src/components/AccountForm.tsx
+++ b/src/components/AccountForm.tsx
@@ -14,12 +14,14 @@ export interface AccountFormProps {
protected?: boolean;
language?: string;
visibility?: PostVisibility;
+ news?: boolean;
};
errors?: {
username?: string;
name?: string;
bio?: string;
};
+ officialAccount: string;
submitLabel: string;
}
@@ -132,6 +134,18 @@ export function AccountForm(props: AccountFormProps) {
+
);
diff --git a/src/components/AccountNewPage.tsx b/src/components/NewAccountPage.tsx
similarity index 90%
rename from src/components/AccountNewPage.tsx
rename to src/components/NewAccountPage.tsx
index afd2213..539ebf0 100644
--- a/src/components/AccountNewPage.tsx
+++ b/src/components/NewAccountPage.tsx
@@ -10,12 +10,14 @@ export interface NewAccountPageProps {
protected?: boolean;
language?: string;
visibility?: PostVisibility;
+ news?: boolean;
};
errors?: {
username?: string;
name?: string;
bio?: string;
};
+ officialAccount: string;
}
export function NewAccountPage(props: NewAccountPageProps) {
@@ -30,6 +32,7 @@ export function NewAccountPage(props: NewAccountPageProps) {
values={props.values}
errors={props.errors}
submitLabel="Create a new account"
+ officialAccount={props.officialAccount}
/>
);
diff --git a/src/federation/account.ts b/src/federation/account.ts
index bfa5bb8..bffd8f0 100644
--- a/src/federation/account.ts
+++ b/src/federation/account.ts
@@ -1,11 +1,15 @@
import {
type Actor,
Announce,
+ type Context,
Create,
type DocumentLoader,
Emoji,
+ Follow,
Link,
PropertyValue,
+ Reject,
+ Undo,
formatSemVer,
getActorHandle,
getActorTypeName,
@@ -36,6 +40,11 @@ import {
updatePostStats,
} from "./post";
+export const REMOTE_ACTOR_FETCH_POSTS = Number.parseInt(
+ // biome-ignore lint/complexity/useLiteralKeys: tsc rants about this (TS4111)
+ process.env["REMOTE_ACTOR_FETCH_POSTS"] ?? "10",
+);
+
export async function persistAccount(
db: PgDatabase<
PostgresJsQueryResultHKT,
@@ -325,3 +334,157 @@ export async function updateAccountStats(
),
);
}
+
+export async function followAccount(
+ db: PgDatabase<
+ PostgresJsQueryResultHKT,
+ typeof schema,
+ ExtractTablesWithRelations
+ >,
+ ctx: Context,
+ follower: schema.Account & { owner: schema.AccountOwner | null },
+ following: schema.Account & { owner: schema.AccountOwner | null },
+ options: {
+ shares?: boolean;
+ notify?: boolean;
+ languages?: string[];
+ } = {},
+): Promise {
+ if (follower.owner == null) {
+ throw new TypeError("Only local accounts can follow other accounts");
+ }
+ const result = await db
+ .insert(schema.follows)
+ .values({
+ iri: new URL(`#follows/${crypto.randomUUID()}`, follower.iri).href,
+ followingId: following.id,
+ followerId: follower.id,
+ shares: options.shares ?? true,
+ notify: options.notify ?? false,
+ languages: options.languages ?? null,
+ approved:
+ following.owner == null || following.protected ? null : new Date(),
+ } satisfies schema.NewFollow)
+ .onConflictDoNothing()
+ .returning();
+ if (result.length < 1) return null;
+ await updateAccountStats(db, follower);
+ await updateAccountStats(db, following);
+ const follow = result[0];
+ if (following.owner == null) {
+ await ctx.sendActivity(
+ { username: follower.owner.handle },
+ [
+ {
+ id: new URL(following.iri),
+ inboxId: new URL(following.inboxUrl),
+ },
+ ],
+ new Follow({
+ id: new URL(follow.iri),
+ actor: new URL(follower.iri),
+ object: new URL(following.iri),
+ }),
+ { excludeBaseUris: [new URL(ctx.origin)] },
+ );
+ }
+ return follow;
+}
+
+export async function unfollowAccount(
+ db: PgDatabase<
+ PostgresJsQueryResultHKT,
+ typeof schema,
+ ExtractTablesWithRelations
+ >,
+ ctx: Context,
+ follower: schema.Account & { owner: schema.AccountOwner | null },
+ following: schema.Account & { owner: schema.AccountOwner | null },
+): Promise {
+ if (follower.owner == null) {
+ throw new TypeError("Only local accounts can unfollow other accounts");
+ }
+ const result = await db
+ .delete(schema.follows)
+ .where(
+ and(
+ eq(schema.follows.followingId, following.id),
+ eq(schema.follows.followerId, follower.id),
+ ),
+ )
+ .returning();
+ if (result.length < 1) return null;
+ await updateAccountStats(db, follower);
+ await updateAccountStats(db, following);
+ if (following.owner == null) {
+ await ctx.sendActivity(
+ { username: follower.owner.handle },
+ [
+ {
+ id: new URL(following.iri),
+ inboxId: new URL(following.inboxUrl),
+ },
+ ],
+ new Undo({
+ id: new URL(`#unfollows/${crypto.randomUUID()}`, follower.iri),
+ actor: new URL(follower.iri),
+ object: new Follow({
+ id: new URL(result[0].iri),
+ actor: new URL(follower.iri),
+ object: new URL(following.iri),
+ }),
+ }),
+ { excludeBaseUris: [new URL(ctx.origin)] },
+ );
+ }
+ return result[0];
+}
+
+export async function removeFollower(
+ db: PgDatabase<
+ PostgresJsQueryResultHKT,
+ typeof schema,
+ ExtractTablesWithRelations
+ >,
+ ctx: Context,
+ following: schema.Account & { owner: schema.AccountOwner | null },
+ follower: schema.Account & { owner: schema.AccountOwner | null },
+): Promise {
+ if (following.owner == null) {
+ throw new TypeError("Only local accounts can remove followers");
+ }
+ const result = await db
+ .delete(schema.follows)
+ .where(
+ and(
+ eq(schema.follows.followingId, following.id),
+ eq(schema.follows.followerId, follower.id),
+ ),
+ )
+ .returning();
+ if (result.length < 1) return null;
+ await ctx.sendActivity(
+ { username: following.owner.handle },
+ {
+ id: new URL(follower.iri),
+ inboxId: new URL(follower.inboxUrl),
+ endpoints:
+ follower.sharedInboxUrl == null
+ ? null
+ : {
+ sharedInbox: new URL(follower.sharedInboxUrl),
+ },
+ },
+ new Reject({
+ id: new URL(`#reject/${crypto.randomUUID()}`, following.iri),
+ actor: new URL(following.iri),
+ object: new Follow({
+ id: new URL(result[0].iri),
+ actor: new URL(follower.iri),
+ object: new URL(following.iri),
+ }),
+ }),
+ { excludeBaseUris: [new URL(ctx.origin)] },
+ );
+ return result[0];
+}
diff --git a/src/federation/inbox.ts b/src/federation/inbox.ts
index 2cfda83..423f101 100644
--- a/src/federation/inbox.ts
+++ b/src/federation/inbox.ts
@@ -17,9 +17,9 @@ import {
type Move,
Note,
Question,
- Reject,
+ type Reject,
type Remove,
- Undo,
+ type Undo,
type Update,
isActor,
} from "@fedify/fedify";
@@ -39,7 +39,12 @@ import {
posts,
reactions,
} from "../schema";
-import { persistAccount, updateAccountStats } from "./account";
+import {
+ persistAccount,
+ removeFollower,
+ unfollowAccount,
+ updateAccountStats,
+} from "./account";
import {
isPost,
persistPollVote,
@@ -263,6 +268,7 @@ export async function onBlocked(
const object = ctx.parseUri(block.objectId);
if (block.objectId == null || object?.type !== "actor") return;
const blocked = await db.query.accountOwners.findFirst({
+ with: { account: true },
where: eq(accountOwners.handle, object.identifier),
});
if (blocked == null) return;
@@ -277,56 +283,18 @@ export async function onBlocked(
.onConflictDoNothing()
.returning();
if (result.length < 1) return;
- const following = await db
- .delete(follows)
- .where(
- and(
- eq(follows.followingId, blockerAccount.id),
- eq(follows.followerId, blocked.id),
- ),
- )
- .returning();
- if (following.length > 0) {
- await ctx.sendActivity(
- object,
- blocker,
- new Undo({
- id: new URL(`#unfollows/${crypto.randomUUID()}`, block.objectId),
- actor: block.objectId,
- object: new Follow({
- id: new URL(following[0].iri),
- actor: block.objectId,
- object: blocker.id,
- }),
- }),
- { excludeBaseUris: [new URL(ctx.origin)] },
- );
- }
- const follower = await db
- .delete(follows)
- .where(
- and(
- eq(follows.followingId, blockerAccount.id),
- eq(follows.followerId, blocked.id),
- ),
- )
- .returning();
- if (follower.length > 0) {
- await ctx.sendActivity(
- object,
- blocker,
- new Reject({
- id: new URL(`#reject/${crypto.randomUUID()}`, block.objectId),
- actor: block.objectId,
- object: new Follow({
- id: new URL(follower[0].iri),
- actor: blocker.id,
- object: block.objectId,
- }),
- }),
- { excludeBaseUris: [new URL(ctx.origin)] },
- );
- }
+ await unfollowAccount(
+ db,
+ ctx,
+ { ...blocked.account, owner: blocked },
+ blockerAccount,
+ );
+ await removeFollower(
+ db,
+ ctx,
+ { ...blocked.account, owner: blocked },
+ blockerAccount,
+ );
}
export async function onUnblocked(
diff --git a/src/pages/accounts.tsx b/src/pages/accounts.tsx
index f45666c..b635e34 100644
--- a/src/pages/accounts.tsx
+++ b/src/pages/accounts.tsx
@@ -11,19 +11,25 @@ import {
isActor,
} from "@fedify/fedify";
import { getLogger } from "@logtape/logtape";
-import { eq, sql } from "drizzle-orm";
+import { and, eq, sql } from "drizzle-orm";
import { uniq } from "es-toolkit";
import { Hono } from "hono";
import { AccountForm } from "../components/AccountForm.tsx";
import { AccountList } from "../components/AccountList.tsx";
+import { DashboardLayout } from "../components/DashboardLayout.tsx";
import {
NewAccountPage,
type NewAccountPageProps,
-} from "../components/AccountNewPage.tsx";
-import { DashboardLayout } from "../components/DashboardLayout.tsx";
+} from "../components/NewAccountPage.tsx";
import db from "../db.ts";
import federation from "../federation";
-import { persistAccount } from "../federation/account.ts";
+import {
+ REMOTE_ACTOR_FETCH_POSTS,
+ followAccount,
+ persistAccount,
+ persistAccountPosts,
+ unfollowAccount,
+} from "../federation/account.ts";
import { loginRequired } from "../login.ts";
import {
type Account,
@@ -36,6 +42,8 @@ import {
} from "../schema.ts";
import { extractCustomEmojis, formatText } from "../text.ts";
+const HOLLO_OFFICIAL_ACCOUNT = "@hollo@hollo.social";
+
const logger = getLogger(["hollo", "pages", "accounts"]);
const accounts = new Hono();
@@ -60,6 +68,7 @@ accounts.post("/", async (c) => {
.get("visibility")
?.toString()
?.trim() as PostVisibility;
+ const news = form.get("news") != null;
if (username == null || username === "" || name == null || name === "") {
return c.html(
{
protected: protected_,
language,
visibility,
+ news,
}}
errors={{
username:
@@ -81,6 +91,7 @@ accounts.post("/", async (c) => {
? "Display name is required."
: undefined,
}}
+ officialAccount={HOLLO_OFFICIAL_ACCOUNT}
/>,
400,
);
@@ -89,7 +100,7 @@ accounts.post("/", async (c) => {
const bioResult = await formatText(db, bio ?? "", fedCtx);
const nameEmojis = await extractCustomEmojis(db, name);
const emojis = { ...nameEmojis, ...bioResult.emojis };
- await db.transaction(async (tx) => {
+ const [account, owner] = await db.transaction(async (tx) => {
await tx
.insert(instances)
.values({
@@ -120,21 +131,40 @@ accounts.post("/", async (c) => {
.returning();
const rsaKeyPair = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
const ed25519KeyPair = await generateCryptoKeyPair("Ed25519");
- await tx.insert(accountOwners).values({
- id: account[0].id,
- handle: username,
- rsaPrivateKeyJwk: await exportJwk(rsaKeyPair.privateKey),
- rsaPublicKeyJwk: await exportJwk(rsaKeyPair.publicKey),
- ed25519PrivateKeyJwk: await exportJwk(ed25519KeyPair.privateKey),
- ed25519PublicKeyJwk: await exportJwk(ed25519KeyPair.publicKey),
- bio: bio ?? "",
- language: language ?? "en",
- visibility: visibility ?? "public",
- });
+ const owner = await tx
+ .insert(accountOwners)
+ .values({
+ id: account[0].id,
+ handle: username,
+ rsaPrivateKeyJwk: await exportJwk(rsaKeyPair.privateKey),
+ rsaPublicKeyJwk: await exportJwk(rsaKeyPair.publicKey),
+ ed25519PrivateKeyJwk: await exportJwk(ed25519KeyPair.privateKey),
+ ed25519PublicKeyJwk: await exportJwk(ed25519KeyPair.publicKey),
+ bio: bio ?? "",
+ language: language ?? "en",
+ visibility: visibility ?? "public",
+ })
+ .returning();
+ return [account[0], owner[0]];
});
const owners = await db.query.accountOwners.findMany({
with: { account: true },
});
+ if (news) {
+ const actor = await fedCtx.lookupObject(HOLLO_OFFICIAL_ACCOUNT);
+ if (isActor(actor)) {
+ await db.transaction(async (tx) => {
+ const following = await persistAccount(tx, actor, fedCtx);
+ if (following != null) {
+ await followAccount(tx, fedCtx, { ...account, owner }, following);
+ await persistAccountPosts(tx, account, REMOTE_ACTOR_FETCH_POSTS, {
+ ...fedCtx,
+ suppressError: true,
+ });
+ }
+ });
+ }
+ }
return c.html();
});
@@ -161,7 +191,12 @@ function AccountListPage({ accountOwners }: AccountListPageProps) {
}
accounts.get("/new", (c) => {
- return c.html();
+ return c.html(
+ ,
+ );
});
accounts.get("/:id", async (c) => {
@@ -170,11 +205,30 @@ accounts.get("/:id", async (c) => {
with: { account: true },
});
if (accountOwner == null) return c.notFound();
- return c.html();
+ const news = await db.query.follows.findFirst({
+ where: and(
+ eq(
+ follows.followingId,
+ db
+ .select({ id: accountsTable.id })
+ .from(accountsTable)
+ .where(eq(accountsTable.handle, HOLLO_OFFICIAL_ACCOUNT)),
+ ),
+ eq(follows.followerId, accountOwner.id),
+ ),
+ });
+ return c.html(
+ ,
+ );
});
interface AccountPageProps extends NewAccountPageProps {
accountOwner: AccountOwner & { account: Account };
+ news: boolean;
}
function AccountPage(props: AccountPageProps) {
@@ -196,8 +250,10 @@ function AccountPage(props: AccountPageProps) {
props.values?.protected ?? props.accountOwner.account.protected,
language: props.values?.language ?? props.accountOwner.language,
visibility: props.values?.visibility ?? props.accountOwner.visibility,
+ news: props.values?.news ?? props.news,
}}
errors={props.errors}
+ officialAccount={HOLLO_OFFICIAL_ACCOUNT}
submitLabel="Save changes"
/>
@@ -219,20 +275,24 @@ accounts.post("/:id", async (c) => {
.get("visibility")
?.toString()
?.trim() as PostVisibility;
+ const news = form.get("news") != null;
if (name == null || name === "") {
return c.html(
,
400,
);
@@ -273,6 +333,20 @@ accounts.post("/:id", async (c) => {
}),
{ preferSharedInbox: true, excludeBaseUris: [fedCtx.url] },
);
+ const account = { ...accountOwner.account, owner: accountOwner };
+ const newsActor = await fedCtx.lookupObject(HOLLO_OFFICIAL_ACCOUNT);
+ if (isActor(newsActor)) {
+ const newsAccount = await persistAccount(db, newsActor, fedCtx);
+ if (newsAccount != null) {
+ if (news) {
+ await followAccount(db, fedCtx, account, newsAccount);
+ await persistAccountPosts(db, newsAccount, REMOTE_ACTOR_FETCH_POSTS, {
+ ...fedCtx,
+ suppressError: true,
+ });
+ } else await unfollowAccount(db, fedCtx, account, newsAccount);
+ }
+ }
return c.redirect("/accounts");
});
@@ -404,7 +478,7 @@ accounts.get("/:id/migrate", async (c) => {