Skip to content

Commit

Permalink
News option
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Nov 2, 2024
1 parent 1564fd6 commit c9ebd1b
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 227 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
185 changes: 30 additions & 155 deletions src/api/v1/accounts.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -19,7 +11,6 @@ import {
gte,
ilike,
inArray,
isNotNull,
isNull,
lt,
lte,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
}),
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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),
Expand Down
14 changes: 14 additions & 0 deletions src/components/AccountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -132,6 +134,18 @@ export function AccountForm(props: AccountFormProps) {
</select>
</label>
</fieldset>
<fieldset>
<label>
<input
type="checkbox"
name="news"
value="true"
checked={props.values?.news}
/>{" "}
Receive news and updates of Hollo by following the official Hollo
account (<tt>{props.officialAccount}</tt>)
</label>
</fieldset>
<button type="submit">{props.submitLabel}</button>
</form>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -30,6 +32,7 @@ export function NewAccountPage(props: NewAccountPageProps) {
values={props.values}
errors={props.errors}
submitLabel="Create a new account"
officialAccount={props.officialAccount}
/>
</DashboardLayout>
);
Expand Down
Loading

0 comments on commit c9ebd1b

Please sign in to comment.