refactor (backend-rs): save nodeinfo cache directly
This commit is contained in:
parent
101c001379
commit
0839fe27b2
7 changed files with 128 additions and 74 deletions
93
packages/backend-rs/index.d.ts
vendored
93
packages/backend-rs/index.d.ts
vendored
|
@ -216,6 +216,7 @@ export function acctToString(acct: Acct): string
|
|||
export function fetchNodeinfo(host: string): Promise<Nodeinfo>
|
||||
export function nodeinfo_2_1(): Promise<any>
|
||||
export function nodeinfo_2_0(): Promise<any>
|
||||
export function updateNodeinfoCache(): Promise<void>
|
||||
/** NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0> */
|
||||
export interface Nodeinfo {
|
||||
/** The schema version, must be 2.0. */
|
||||
|
@ -241,16 +242,16 @@ export interface Software20 {
|
|||
version: string
|
||||
}
|
||||
export enum Protocol {
|
||||
Activitypub = 'activitypub',
|
||||
Buddycloud = 'buddycloud',
|
||||
Dfrn = 'dfrn',
|
||||
Diaspora = 'diaspora',
|
||||
Libertree = 'libertree',
|
||||
Ostatus = 'ostatus',
|
||||
Pumpio = 'pumpio',
|
||||
Tent = 'tent',
|
||||
Xmpp = 'xmpp',
|
||||
Zot = 'zot'
|
||||
Activitypub = 0,
|
||||
Buddycloud = 1,
|
||||
Dfrn = 2,
|
||||
Diaspora = 3,
|
||||
Libertree = 4,
|
||||
Ostatus = 5,
|
||||
Pumpio = 6,
|
||||
Tent = 7,
|
||||
Xmpp = 8,
|
||||
Zot = 9
|
||||
}
|
||||
/** The third party sites this server can connect to via their application API. */
|
||||
export interface Services {
|
||||
|
@ -261,45 +262,45 @@ export interface Services {
|
|||
}
|
||||
/** The third party sites this server can retrieve messages from for combined display with regular traffic. */
|
||||
export enum Inbound {
|
||||
Atom1 = 'atom1',
|
||||
Gnusocial = 'gnusocial',
|
||||
Imap = 'imap',
|
||||
Pnut = 'pnut',
|
||||
Pop3 = 'pop3',
|
||||
Pumpio = 'pumpio',
|
||||
Rss2 = 'rss2',
|
||||
Twitter = 'twitter'
|
||||
Atom1 = 0,
|
||||
Gnusocial = 1,
|
||||
Imap = 2,
|
||||
Pnut = 3,
|
||||
Pop3 = 4,
|
||||
Pumpio = 5,
|
||||
Rss2 = 6,
|
||||
Twitter = 7
|
||||
}
|
||||
/** The third party sites this server can publish messages to on the behalf of a user. */
|
||||
export enum Outbound {
|
||||
Atom1 = 'atom1',
|
||||
Blogger = 'blogger',
|
||||
Buddycloud = 'buddycloud',
|
||||
Diaspora = 'diaspora',
|
||||
Dreamwidth = 'dreamwidth',
|
||||
Drupal = 'drupal',
|
||||
Facebook = 'facebook',
|
||||
Friendica = 'friendica',
|
||||
Gnusocial = 'gnusocial',
|
||||
Google = 'google',
|
||||
Insanejournal = 'insanejournal',
|
||||
Libertree = 'libertree',
|
||||
Linkedin = 'linkedin',
|
||||
Livejournal = 'livejournal',
|
||||
Mediagoblin = 'mediagoblin',
|
||||
Myspace = 'myspace',
|
||||
Pinterest = 'pinterest',
|
||||
Pnut = 'pnut',
|
||||
Posterous = 'posterous',
|
||||
Pumpio = 'pumpio',
|
||||
Redmatrix = 'redmatrix',
|
||||
Rss2 = 'rss2',
|
||||
Smtp = 'smtp',
|
||||
Tent = 'tent',
|
||||
Tumblr = 'tumblr',
|
||||
Twitter = 'twitter',
|
||||
Wordpress = 'wordpress',
|
||||
Xmpp = 'xmpp'
|
||||
Atom1 = 0,
|
||||
Blogger = 1,
|
||||
Buddycloud = 2,
|
||||
Diaspora = 3,
|
||||
Dreamwidth = 4,
|
||||
Drupal = 5,
|
||||
Facebook = 6,
|
||||
Friendica = 7,
|
||||
Gnusocial = 8,
|
||||
Google = 9,
|
||||
Insanejournal = 10,
|
||||
Libertree = 11,
|
||||
Linkedin = 12,
|
||||
Livejournal = 13,
|
||||
Mediagoblin = 14,
|
||||
Myspace = 15,
|
||||
Pinterest = 16,
|
||||
Pnut = 17,
|
||||
Posterous = 18,
|
||||
Pumpio = 19,
|
||||
Redmatrix = 20,
|
||||
Rss2 = 21,
|
||||
Smtp = 22,
|
||||
Tent = 23,
|
||||
Tumblr = 24,
|
||||
Twitter = 25,
|
||||
Wordpress = 26,
|
||||
Xmpp = 27
|
||||
}
|
||||
/** Usage statistics for this server. */
|
||||
export interface Usage {
|
||||
|
|
|
@ -310,7 +310,7 @@ if (!nativeBinding) {
|
|||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, updateNodeinfoCache, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
||||
|
||||
module.exports.SECOND = SECOND
|
||||
module.exports.MINUTE = MINUTE
|
||||
|
@ -328,6 +328,7 @@ module.exports.acctToString = acctToString
|
|||
module.exports.fetchNodeinfo = fetchNodeinfo
|
||||
module.exports.nodeinfo_2_1 = nodeinfo_2_1
|
||||
module.exports.nodeinfo_2_0 = nodeinfo_2_0
|
||||
module.exports.updateNodeinfoCache = updateNodeinfoCache
|
||||
module.exports.Protocol = Protocol
|
||||
module.exports.Inbound = Inbound
|
||||
module.exports.Outbound = Outbound
|
||||
|
|
|
@ -2,21 +2,27 @@
|
|||
|
||||
use crate::{
|
||||
config::{local_server_info, CONFIG},
|
||||
database::{cache, db_conn},
|
||||
database::db_conn,
|
||||
federation::nodeinfo::schema::*,
|
||||
model::entity::{note, user},
|
||||
};
|
||||
use sea_orm::prelude::*;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
static CACHE: Mutex<Option<Nodeinfo21>> = Mutex::new(None);
|
||||
|
||||
fn set_cache(nodeinfo: &Nodeinfo21) {
|
||||
let _ = CACHE
|
||||
.lock()
|
||||
.map(|mut cache| *cache = Some(nodeinfo.to_owned()));
|
||||
}
|
||||
|
||||
/// Errors that can occur while generating NodeInfo of the local server
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Database error: {0}")]
|
||||
Db(#[from] DbErr),
|
||||
#[error("Cache error: {0}")]
|
||||
Cache(#[from] cache::Error),
|
||||
#[error("Failed to serialize nodeinfo to JSON: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
@ -62,10 +68,12 @@ async fn statistics() -> Result<(u64, u64, u64, u64), DbErr> {
|
|||
/// Generates NodeInfo (version 2.1) of the local server.
|
||||
/// This function doesn't use caches and returns the latest information.
|
||||
async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||
tracing::info!("generating NodeInfo");
|
||||
|
||||
let (local_users, local_active_halfyear, local_active_month, local_posts) =
|
||||
statistics().await?;
|
||||
let meta = local_server_info().await?;
|
||||
let metadata = HashMap::from([
|
||||
let mut metadata = HashMap::from([
|
||||
(
|
||||
"nodeName".to_string(),
|
||||
json!(meta.name.unwrap_or_else(|| CONFIG.host.clone())),
|
||||
|
@ -98,6 +106,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
|||
json!(meta.theme_color.unwrap_or_else(|| "#31748f".to_string())),
|
||||
),
|
||||
]);
|
||||
metadata.shrink_to_fit();
|
||||
|
||||
Ok(Nodeinfo21 {
|
||||
version: "2.1".to_string(),
|
||||
|
@ -126,19 +135,24 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
|||
})
|
||||
}
|
||||
|
||||
async fn nodeinfo_2_1_impl(use_cache: bool) -> Result<Nodeinfo21, Error> {
|
||||
if use_cache {
|
||||
if let Some(nodeinfo) = CACHE.lock().ok().and_then(|cache| cache.to_owned()) {
|
||||
return Ok(nodeinfo);
|
||||
}
|
||||
}
|
||||
|
||||
let nodeinfo = generate_nodeinfo_2_1().await?;
|
||||
|
||||
tracing::info!("updating cache");
|
||||
set_cache(&nodeinfo);
|
||||
|
||||
Ok(nodeinfo)
|
||||
}
|
||||
|
||||
/// Returns NodeInfo (version 2.1) of the local server.
|
||||
pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||
const NODEINFO_2_1_CACHE_KEY: &str = "nodeinfo_2_1";
|
||||
|
||||
let cached = cache::get::<Nodeinfo21>(NODEINFO_2_1_CACHE_KEY).await?;
|
||||
|
||||
if let Some(nodeinfo) = cached {
|
||||
Ok(nodeinfo)
|
||||
} else {
|
||||
let nodeinfo = generate_nodeinfo_2_1().await?;
|
||||
cache::set(NODEINFO_2_1_CACHE_KEY, &nodeinfo, 60 * 60).await?;
|
||||
Ok(nodeinfo)
|
||||
}
|
||||
nodeinfo_2_1_impl(true).await
|
||||
}
|
||||
|
||||
/// Returns NodeInfo (version 2.0) of the local server.
|
||||
|
@ -155,3 +169,9 @@ pub async fn nodeinfo_2_1_as_json() -> Result<serde_json::Value, Error> {
|
|||
pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
|
||||
Ok(serde_json::to_value(nodeinfo_2_0().await?)?)
|
||||
}
|
||||
|
||||
#[crate::ts_export(js_name = "updateNodeinfoCache")]
|
||||
pub async fn update_cache() -> Result<(), Error> {
|
||||
nodeinfo_2_1_impl(false).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ use std::collections::HashMap;
|
|||
// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
|
||||
|
||||
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Deserialize))]
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Nodeinfo21 {
|
||||
/// The schema version, must be 2.1.
|
||||
|
@ -53,8 +53,8 @@ pub struct Nodeinfo20 {
|
|||
}
|
||||
|
||||
/// Metadata about server software in use (version 2.1).
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Deserialize))]
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Software21 {
|
||||
/// The canonical name of this server software.
|
||||
|
@ -82,7 +82,7 @@ pub struct Software20 {
|
|||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[crate::export(string_enum = "lowercase")]
|
||||
#[crate::derive_clone_and_export]
|
||||
pub enum Protocol {
|
||||
Activitypub,
|
||||
Buddycloud,
|
||||
|
@ -98,7 +98,7 @@ pub enum Protocol {
|
|||
|
||||
/// The third party sites this server can connect to via their application API.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[crate::export(object)]
|
||||
pub struct Services {
|
||||
|
@ -112,7 +112,7 @@ pub struct Services {
|
|||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[crate::export(string_enum = "lowercase")]
|
||||
#[crate::derive_clone_and_export]
|
||||
pub enum Inbound {
|
||||
#[serde(rename = "atom1.0")]
|
||||
Atom1,
|
||||
|
@ -131,7 +131,7 @@ pub enum Inbound {
|
|||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[crate::export(string_enum = "lowercase")]
|
||||
#[crate::derive_clone_and_export]
|
||||
pub enum Outbound {
|
||||
#[serde(rename = "atom1.0")]
|
||||
Atom1,
|
||||
|
@ -167,7 +167,7 @@ pub enum Outbound {
|
|||
|
||||
/// Usage statistics for this server.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[crate::export(object)]
|
||||
pub struct Usage {
|
||||
|
@ -178,7 +178,7 @@ pub struct Usage {
|
|||
|
||||
/// statistics about the users of this server.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[crate::export(object)]
|
||||
pub struct Users {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use macro_rs::{export, ts_export};
|
||||
use macro_rs::{derive_clone_and_export, export, ts_export};
|
||||
|
||||
pub mod config;
|
||||
pub mod database;
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
removeOldAttestationChallenges,
|
||||
showServerInfo,
|
||||
updateMetaCache,
|
||||
updateNodeinfoCache,
|
||||
type Config,
|
||||
} from "backend-rs";
|
||||
import { config } from "@/config.js";
|
||||
|
@ -51,6 +52,8 @@ export async function masterMain() {
|
|||
import("../daemons/queue-stats.js").then((x) => x.default());
|
||||
// Update meta cache every 5 minitues
|
||||
setInterval(() => updateMetaCache(), 1000 * 60 * 5);
|
||||
// Update nodeinfo cache every hour
|
||||
setInterval(() => updateNodeinfoCache(), 1000 * 60 * 60);
|
||||
// Remove old attestation challenges
|
||||
setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,35 @@ pub fn read_version_from_package_json(_item: proc_macro::TokenStream) -> proc_ma
|
|||
quote! { #version }.into()
|
||||
}
|
||||
|
||||
/// Export an enum to TypeScript, and derive [Clone].
|
||||
///
|
||||
/// You need this macro because [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
||||
/// automatically derives the [Clone] trait for enums and causes conflicts.
|
||||
///
|
||||
/// This is a wrapper of [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
|
||||
/// that expands to
|
||||
/// ```no_run
|
||||
/// #[cfg_attr(not(feature = "napi"), derive(Clone))]
|
||||
/// #[cfg_attr(feature = "napi", napi_derive::napi(attr))]
|
||||
/// # fn f() {} // to work around doc test compilation error
|
||||
/// ```
|
||||
/// where `attr` is given attribute(s).
|
||||
#[proc_macro_attribute]
|
||||
pub fn derive_clone_and_export(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let attr: TokenStream = attr.into();
|
||||
let item: TokenStream = item.into();
|
||||
|
||||
quote! {
|
||||
#[cfg_attr(not(feature = "napi"), derive(Clone))]
|
||||
#[cfg_attr(feature = "napi", napi_derive::napi(#attr))]
|
||||
#item
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Export a function, struct, enum, const, etc. to TypeScript.
|
||||
///
|
||||
/// This is a wrapper of [macro@napi] that expands to
|
||||
|
|
Loading…
Reference in a new issue