refactor (backend): port getInstanceActor/getRelayActor to backend-rs
This commit is contained in:
parent
9f22a43229
commit
9bb357dc50
12 changed files with 155 additions and 63 deletions
5
packages/backend-rs/index.d.ts
vendored
5
packages/backend-rs/index.d.ts
vendored
|
@ -461,6 +461,8 @@ export declare function getFullApAccount(username: string, host?: string | undef
|
|||
|
||||
export declare function getImageSizeFromUrl(url: string): Promise<ImageSize>
|
||||
|
||||
export declare function getInternalActor(actor: InternalActor): Promise<User>
|
||||
|
||||
export declare function getNoteSummary(fileIds: Array<string>, text: string | undefined | null, cw: string | undefined | null, hasPoll: boolean): string
|
||||
|
||||
export declare function getTimestamp(id: string): number
|
||||
|
@ -542,6 +544,9 @@ export interface Instance {
|
|||
faviconUrl: string | null
|
||||
}
|
||||
|
||||
export type InternalActor = 'instance'|
|
||||
'relay';
|
||||
|
||||
/**
|
||||
* Checks if a server is allowlisted.
|
||||
* Returns `Ok(true)` if private mode is disabled.
|
||||
|
|
|
@ -386,6 +386,7 @@ module.exports.genId = nativeBinding.genId
|
|||
module.exports.genIdAt = nativeBinding.genIdAt
|
||||
module.exports.getFullApAccount = nativeBinding.getFullApAccount
|
||||
module.exports.getImageSizeFromUrl = nativeBinding.getImageSizeFromUrl
|
||||
module.exports.getInternalActor = nativeBinding.getInternalActor
|
||||
module.exports.getNoteSummary = nativeBinding.getNoteSummary
|
||||
module.exports.getTimestamp = nativeBinding.getTimestamp
|
||||
module.exports.greet = nativeBinding.greet
|
||||
|
@ -393,6 +394,7 @@ module.exports.hashPassword = nativeBinding.hashPassword
|
|||
module.exports.HOUR = nativeBinding.HOUR
|
||||
module.exports.Inbound = nativeBinding.Inbound
|
||||
module.exports.initializeRustLogger = nativeBinding.initializeRustLogger
|
||||
module.exports.InternalActor = nativeBinding.InternalActor
|
||||
module.exports.isAllowedServer = nativeBinding.isAllowedServer
|
||||
module.exports.isBlockedServer = nativeBinding.isBlockedServer
|
||||
module.exports.isOldPasswordAlgorithm = nativeBinding.isOldPasswordAlgorithm
|
||||
|
|
|
@ -8,7 +8,7 @@ pub struct Acct {
|
|||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[doc = "Error type to indicate a string-to-[`Acct`] conversion failure"]
|
||||
#[doc = "Error type to indicate a [`String`]-to-[`Acct`] conversion failure"]
|
||||
#[error("failed to convert string '{0}' into acct")]
|
||||
pub struct InvalidAcctString(String);
|
||||
|
||||
|
|
85
packages/backend-rs/src/federation/internal_actor/cache.rs
Normal file
85
packages/backend-rs/src/federation/internal_actor/cache.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
//! In-memory internal actor cache handler
|
||||
|
||||
// TODO: refactoring
|
||||
|
||||
use super::*;
|
||||
use crate::{database::db_conn, model::entity::user};
|
||||
use sea_orm::prelude::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
#[doc = "database error"]
|
||||
Db(#[from] DbErr),
|
||||
#[error("{} does not exist", Acct::from(.0.to_owned()))]
|
||||
#[doc = "internal actor does not exist"]
|
||||
InternalActorNotFound(InternalActor),
|
||||
}
|
||||
|
||||
static INSTANCE_ACTOR: Mutex<Option<Arc<user::Model>>> = Mutex::new(None);
|
||||
static RELAY_ACTOR: Mutex<Option<Arc<user::Model>>> = Mutex::new(None);
|
||||
|
||||
fn set_instance_actor(value: Arc<user::Model>) {
|
||||
let _ = INSTANCE_ACTOR.lock().map(|mut cache| *cache = Some(value));
|
||||
}
|
||||
fn set_relay_actor(value: Arc<user::Model>) {
|
||||
let _ = RELAY_ACTOR.lock().map(|mut cache| *cache = Some(value));
|
||||
}
|
||||
|
||||
async fn cache_instance_actor() -> Result<Arc<user::Model>, Error> {
|
||||
let actor = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(INSTANCE_ACTOR_USERNAME))
|
||||
.filter(user::Column::Host.is_null())
|
||||
.one(db_conn().await?)
|
||||
.await?;
|
||||
|
||||
if let Some(actor) = actor {
|
||||
let arc = Arc::new(actor);
|
||||
set_instance_actor(arc.clone());
|
||||
Ok(arc)
|
||||
} else {
|
||||
Err(Error::InternalActorNotFound(InternalActor::Instance))
|
||||
}
|
||||
}
|
||||
async fn cache_relay_actor() -> Result<Arc<user::Model>, Error> {
|
||||
let actor = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(RELAY_ACTOR_USERNAME))
|
||||
.filter(user::Column::Host.is_null())
|
||||
.one(db_conn().await?)
|
||||
.await?;
|
||||
|
||||
if let Some(actor) = actor {
|
||||
let arc = Arc::new(actor);
|
||||
set_relay_actor(arc.clone());
|
||||
Ok(arc)
|
||||
} else {
|
||||
Err(Error::InternalActorNotFound(InternalActor::Relay))
|
||||
}
|
||||
}
|
||||
|
||||
// for napi export
|
||||
// https://github.com/napi-rs/napi-rs/issues/2060
|
||||
type User = user::Model;
|
||||
|
||||
#[macros::export(js_name = "getInternalActor")]
|
||||
pub async fn get(actor: InternalActor) -> Result<Arc<User>, Error> {
|
||||
match actor {
|
||||
InternalActor::Instance => {
|
||||
if let Some(cache) = INSTANCE_ACTOR.lock().ok().and_then(|cache| cache.clone()) {
|
||||
tracing::debug!("Using cached instance.actor");
|
||||
return Ok(cache);
|
||||
}
|
||||
tracing::debug!("Caching instance.actor");
|
||||
cache_instance_actor().await
|
||||
}
|
||||
InternalActor::Relay => {
|
||||
if let Some(cache) = RELAY_ACTOR.lock().ok().and_then(|cache| cache.clone()) {
|
||||
tracing::debug!("Using cached relay.actor");
|
||||
return Ok(cache);
|
||||
}
|
||||
tracing::debug!("Caching relay.actor");
|
||||
cache_relay_actor().await
|
||||
}
|
||||
}
|
||||
}
|
34
packages/backend-rs/src/federation/internal_actor/mod.rs
Normal file
34
packages/backend-rs/src/federation/internal_actor/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
mod cache;
|
||||
|
||||
pub use cache::get;
|
||||
|
||||
use super::acct::Acct;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[macros::derive_clone_and_export(string_enum = "lowercase")]
|
||||
pub enum InternalActor {
|
||||
Instance,
|
||||
Relay,
|
||||
}
|
||||
|
||||
const INSTANCE_ACTOR_USERNAME: &str = "instance.actor";
|
||||
const RELAY_ACTOR_USERNAME: &str = "relay.actor";
|
||||
|
||||
// TODO: When `std::mem::variant_count` is stabilized, use
|
||||
// it to count system actors instead of hard coding the magic number
|
||||
pub const INTERNAL_ACTORS: u64 = 2;
|
||||
|
||||
impl From<InternalActor> for Acct {
|
||||
fn from(actor: InternalActor) -> Self {
|
||||
match actor {
|
||||
InternalActor::Instance => Acct {
|
||||
username: INSTANCE_ACTOR_USERNAME.to_owned(),
|
||||
host: None,
|
||||
},
|
||||
InternalActor::Relay => Acct {
|
||||
username: RELAY_ACTOR_USERNAME.to_owned(),
|
||||
host: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
//! Services used to federate with other servers
|
||||
|
||||
pub mod acct;
|
||||
pub mod internal_actor;
|
||||
pub mod nodeinfo;
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
use crate::model::entity::user;
|
||||
use crate::{federation::internal_actor::INTERNAL_ACTORS, model::entity::user};
|
||||
use sea_orm::prelude::*;
|
||||
|
||||
// TODO: When `std::mem::variant_count` is stabilized, use
|
||||
// it to count system actors instead of hard coding the magic number
|
||||
|
||||
// @instance.actor and @relay.actor are not real users
|
||||
const NUMBER_OF_SYSTEM_ACTORS: u64 = 2;
|
||||
|
||||
pub async fn local_total(db: &DbConn) -> Result<u64, DbErr> {
|
||||
user::Entity::find()
|
||||
.filter(user::Column::Host.is_null())
|
||||
.count(db)
|
||||
.await
|
||||
.map(|count| count - NUMBER_OF_SYSTEM_ACTORS)
|
||||
.map(|count| count - INTERNAL_ACTORS)
|
||||
}
|
||||
|
||||
#[macros::ts_export(js_name = "countLocalUsers")]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { config } from "@/config.js";
|
||||
import type { ILocalUser } from "@/models/entities/user.js";
|
||||
import { getInstanceActor } from "@/services/instance-actor.js";
|
||||
import {
|
||||
extractHost,
|
||||
getInternalActor,
|
||||
isAllowedServer,
|
||||
isBlockedServer,
|
||||
isSelfHost,
|
||||
|
@ -112,7 +112,7 @@ export default class Resolver {
|
|||
}
|
||||
|
||||
if (!this.user) {
|
||||
this.user = await getInstanceActor();
|
||||
this.user = await getInternalActor("instance");
|
||||
}
|
||||
|
||||
apLogger.info(
|
||||
|
@ -158,7 +158,7 @@ export default class Resolver {
|
|||
if (!parsed.local) throw new Error("resolveLocal: not local");
|
||||
|
||||
switch (parsed.type) {
|
||||
case "notes":
|
||||
case "notes": {
|
||||
const note = await Notes.findOneByOrFail({ id: parsed.id });
|
||||
if (parsed.rest === "activity") {
|
||||
// this refers to the create activity and not the note itself
|
||||
|
@ -166,20 +166,24 @@ export default class Resolver {
|
|||
} else {
|
||||
return renderNote(note);
|
||||
}
|
||||
case "users":
|
||||
}
|
||||
case "users": {
|
||||
const user = await Users.findOneByOrFail({ id: parsed.id });
|
||||
return await renderPerson(user as ILocalUser);
|
||||
case "questions":
|
||||
}
|
||||
case "questions": {
|
||||
// Polls are indexed by the note they are attached to.
|
||||
const [pollNote, poll] = await Promise.all([
|
||||
Notes.findOneByOrFail({ id: parsed.id }),
|
||||
Polls.findOneByOrFail({ noteId: parsed.id }),
|
||||
]);
|
||||
return await renderQuestion({ id: pollNote.userId }, pollNote, poll);
|
||||
case "likes":
|
||||
}
|
||||
case "likes": {
|
||||
const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id });
|
||||
return renderActivity(renderLike(reaction, { uri: null }));
|
||||
case "follows":
|
||||
}
|
||||
case "follows": {
|
||||
// if rest is a <followee id>
|
||||
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
||||
const [follower, followee] = await Promise.all(
|
||||
|
@ -207,6 +211,7 @@ export default class Resolver {
|
|||
throw new Error("resolveLocal: invalid follow URI");
|
||||
}
|
||||
return renderActivity(renderFollow(follower, followee, url));
|
||||
}
|
||||
default:
|
||||
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import renderKey from "@/remote/activitypub/renderer/key.js";
|
|||
import { renderPerson } from "@/remote/activitypub/renderer/person.js";
|
||||
import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
|
||||
import { inbox as processInbox } from "@/queue/index.js";
|
||||
import { fetchMeta, isSelfHost } from "backend-rs";
|
||||
import { fetchMeta, getInternalActor, isSelfHost } from "backend-rs";
|
||||
import {
|
||||
Notes,
|
||||
Users,
|
||||
|
@ -24,7 +24,6 @@ import {
|
|||
checkFetch,
|
||||
getSignatureUser,
|
||||
} from "@/remote/activitypub/check-fetch.js";
|
||||
import { getInstanceActor } from "@/services/instance-actor.js";
|
||||
import renderFollow from "@/remote/activitypub/renderer/follow.js";
|
||||
import Featured from "./activitypub/featured.js";
|
||||
import Following from "./activitypub/following.js";
|
||||
|
@ -296,7 +295,7 @@ router.get("/users/:user/collections/featured", Featured);
|
|||
|
||||
// publickey
|
||||
router.get("/users/:user/publickey", async (ctx) => {
|
||||
const instanceActor = await getInstanceActor();
|
||||
const instanceActor = await getInternalActor("instance");
|
||||
if (ctx.params.user === instanceActor.id) {
|
||||
ctx.body = renderActivity(
|
||||
renderKey(instanceActor, await getUserKeypair(instanceActor.id)),
|
||||
|
@ -360,7 +359,7 @@ async function userInfo(ctx: Router.RouterContext, user: User | null) {
|
|||
router.get("/users/:user", async (ctx, next) => {
|
||||
if (!isActivityPubReq(ctx)) return await next();
|
||||
|
||||
const instanceActor = await getInstanceActor();
|
||||
const instanceActor = await getInternalActor("instance");
|
||||
if (ctx.params.user === instanceActor.id) {
|
||||
await userInfo(ctx, instanceActor);
|
||||
return;
|
||||
|
@ -387,7 +386,7 @@ router.get("/@:user", async (ctx, next) => {
|
|||
if (!isActivityPubReq(ctx)) return await next();
|
||||
|
||||
if (ctx.params.user === "instance.actor") {
|
||||
const instanceActor = await getInstanceActor();
|
||||
const instanceActor = await getInternalActor("instance");
|
||||
await userInfo(ctx, instanceActor);
|
||||
return;
|
||||
}
|
||||
|
@ -407,8 +406,8 @@ router.get("/@:user", async (ctx, next) => {
|
|||
await userInfo(ctx, user);
|
||||
});
|
||||
|
||||
router.get("/actor", async (ctx, next) => {
|
||||
const instanceActor = await getInstanceActor();
|
||||
router.get("/actor", async (ctx, _next) => {
|
||||
const instanceActor = await getInternalActor("instance");
|
||||
await userInfo(ctx, instanceActor);
|
||||
});
|
||||
//#endregion
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import define from "@/server/api/define.js";
|
||||
import { AbuseUserReports, Users } from "@/models/index.js";
|
||||
import { getInstanceActor } from "@/services/instance-actor.js";
|
||||
import { getInternalActor } from "backend-rs";
|
||||
import { deliver } from "@/queue/index.js";
|
||||
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
|
||||
import { renderFlag } from "@/remote/activitypub/renderer/flag.js";
|
||||
|
@ -29,7 +29,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
}
|
||||
|
||||
if (ps.forward && report.targetUserHost != null) {
|
||||
const actor = await getInstanceActor();
|
||||
const actor = await getInternalActor("instance");
|
||||
const targetUser = await Users.findOneByOrFail({ id: report.targetUserId });
|
||||
|
||||
deliver(
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import type { ILocalUser } from "@/models/entities/user.js";
|
||||
import { Users } from "@/models/index.js";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
import { IsNull } from "typeorm";
|
||||
|
||||
const ACTOR_USERNAME = "instance.actor" as const;
|
||||
|
||||
const cache = new Cache<ILocalUser>("instanceActor", 60 * 30);
|
||||
|
||||
export async function getInstanceActor(): Promise<ILocalUser> {
|
||||
const cached = await cache.get(null, true);
|
||||
if (cached) return cached;
|
||||
|
||||
const user = (await Users.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
})) as ILocalUser;
|
||||
|
||||
await cache.set(null, user);
|
||||
return user;
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import { IsNull } from "typeorm";
|
||||
import { renderFollowRelay } from "@/remote/activitypub/renderer/follow-relay.js";
|
||||
import {
|
||||
renderActivity,
|
||||
|
@ -6,25 +5,14 @@ import {
|
|||
} from "@/remote/activitypub/renderer/index.js";
|
||||
import renderUndo from "@/remote/activitypub/renderer/undo.js";
|
||||
import { deliver } from "@/queue/index.js";
|
||||
import type { ILocalUser, User } from "@/models/entities/user.js";
|
||||
import { Users, Relays } from "@/models/index.js";
|
||||
import { genId } from "backend-rs";
|
||||
import type { User } from "@/models/entities/user.js";
|
||||
import { Relays } from "@/models/index.js";
|
||||
import { getInternalActor, genId } from "backend-rs";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
import type { Relay } from "@/models/entities/relay.js";
|
||||
|
||||
const ACTOR_USERNAME = "relay.actor" as const;
|
||||
|
||||
const relaysCache = new Cache<Relay[]>("relay", 60 * 60);
|
||||
|
||||
export async function getRelayActor(): Promise<ILocalUser> {
|
||||
const user = await Users.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
});
|
||||
|
||||
return user as ILocalUser;
|
||||
}
|
||||
|
||||
export async function addRelay(inbox: string) {
|
||||
const relay = await Relays.insert({
|
||||
id: genId(),
|
||||
|
@ -32,7 +20,7 @@ export async function addRelay(inbox: string) {
|
|||
status: "requesting",
|
||||
}).then((x) => Relays.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
const relayActor = await getRelayActor();
|
||||
const relayActor = await getInternalActor("relay");
|
||||
const follow = renderFollowRelay(relay, relayActor);
|
||||
const activity = renderActivity(follow);
|
||||
deliver(relayActor, activity, relay.inbox);
|
||||
|
@ -49,7 +37,7 @@ export async function removeRelay(inbox: string) {
|
|||
throw new Error("relay not found");
|
||||
}
|
||||
|
||||
const relayActor = await getRelayActor();
|
||||
const relayActor = await getInternalActor("relay");
|
||||
const follow = renderFollowRelay(relay, relayActor);
|
||||
const undo = renderUndo(follow, relayActor);
|
||||
const activity = renderActivity(undo);
|
||||
|
|
Loading…
Reference in a new issue