diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 6b92608f7f..2297322ec9 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -1352,6 +1352,7 @@ export interface Webhook { latestStatus: number | null } export function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, noteMutedUsers: Array): Promise +export function updateAntennaCache(): Promise export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise export function unwatchNote(watcherId: string, noteId: string): Promise export enum PushNotificationKind { diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index ab79e101c8..1801db3f63 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -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, 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, 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 @@ -378,6 +378,7 @@ module.exports.UserEmojiModPerm = UserEmojiModPerm module.exports.UserProfileFfvisibility = UserProfileFfvisibility module.exports.UserProfileMutingNotificationTypes = UserProfileMutingNotificationTypes module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote +module.exports.updateAntennaCache = updateAntennaCache module.exports.watchNote = watchNote module.exports.unwatchNote = unwatchNote module.exports.PushNotificationKind = PushNotificationKind diff --git a/packages/backend-rs/src/service/antenna/cache.rs b/packages/backend-rs/src/service/antenna/cache.rs new file mode 100644 index 0000000000..65bd6efca9 --- /dev/null +++ b/packages/backend-rs/src/service/antenna/cache.rs @@ -0,0 +1,27 @@ +//! In-memory antennas cache handler + +use crate::{database::db_conn, model::entity::antenna}; +use sea_orm::prelude::*; +use std::sync::Mutex; + +static CACHE: Mutex>> = Mutex::new(None); + +fn set(antennas: &[antenna::Model]) { + let _ = CACHE + .lock() + .map(|mut cache| *cache = Some(antennas.to_owned())); +} + +pub(super) async fn update() -> Result, DbErr> { + tracing::debug!("updating cache"); + let antennas = antenna::Entity::find().all(db_conn().await?).await?; + set(&antennas); + Ok(antennas) +} + +pub(super) async fn get() -> Result, DbErr> { + if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) { + return Ok(cache); + } + update().await +} diff --git a/packages/backend-rs/src/service/antenna/mod.rs b/packages/backend-rs/src/service/antenna/mod.rs index 607d5aa95c..2f255581ed 100644 --- a/packages/backend-rs/src/service/antenna/mod.rs +++ b/packages/backend-rs/src/service/antenna/mod.rs @@ -1,2 +1,4 @@ +mod cache; mod check_hit; pub mod process_new_note; +pub mod update; diff --git a/packages/backend-rs/src/service/antenna/process_new_note.rs b/packages/backend-rs/src/service/antenna/process_new_note.rs index 1af4e93433..853fbccbe1 100644 --- a/packages/backend-rs/src/service/antenna/process_new_note.rs +++ b/packages/backend-rs/src/service/antenna/process_new_note.rs @@ -1,10 +1,13 @@ use crate::{ - database::{cache, db_conn, redis_conn, redis_key, RedisConnError}, + database::{cache, redis_conn, redis_key, RedisConnError}, federation::acct::Acct, misc::get_note_all_texts::{all_texts, PartialNoteToElaborate}, - model::entity::{antenna, note}, - service::antenna::check_hit::{check_hit_antenna, AntennaCheckError}, - service::stream, + model::entity::note, + service::{ + antenna, + antenna::check_hit::{check_hit_antenna, AntennaCheckError}, + stream, + }, util::id::{get_timestamp, InvalidIdError}, }; use redis::{streams::StreamMaxlen, AsyncCommands, RedisError}; @@ -32,20 +35,6 @@ pub enum Error { // https://github.com/napi-rs/napi-rs/issues/2060 type Note = note::Model; -// TODO?: it might be better to store this directly in memory -// (like fetch_meta) instead of Redis as it's used so much -async fn antennas() -> Result, Error> { - const CACHE_KEY: &str = "antennas"; - - if let Some(antennas) = cache::get::>(CACHE_KEY).await? { - Ok(antennas) - } else { - let antennas = antenna::Entity::find().all(db_conn().await?).await?; - cache::set(CACHE_KEY, &antennas, 5 * 60).await?; - Ok(antennas) - } -} - #[crate::export] pub async fn update_antennas_on_new_note( note: Note, @@ -67,7 +56,7 @@ pub async fn update_antennas_on_new_note( .await?; // TODO: do this in parallel - for antenna in antennas().await?.iter() { + for antenna in antenna::cache::get().await?.iter() { if note_muted_users.contains(&antenna.user_id) { continue; } diff --git a/packages/backend-rs/src/service/antenna/update.rs b/packages/backend-rs/src/service/antenna/update.rs new file mode 100644 index 0000000000..350b9ba9db --- /dev/null +++ b/packages/backend-rs/src/service/antenna/update.rs @@ -0,0 +1,7 @@ +//! This module is (currently) used in the TypeScript backend only. + +#[crate::ts_export] +pub async fn update_antenna_cache() -> Result<(), sea_orm::DbErr> { + super::cache::update().await?; + Ok(()) +} diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 00b0766d88..dc16f9290f 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -1,5 +1,5 @@ import define from "@/server/api/define.js"; -import { fetchMeta, genIdAt } from "backend-rs"; +import { fetchMeta, genIdAt, updateAntennaCache } from "backend-rs"; import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js"; import { ApiError } from "@/server/api/error.js"; import { publishInternalEvent } from "@/services/stream.js"; @@ -171,8 +171,9 @@ export default define(meta, paramDef, async (ps, user) => { withFile: ps.withFile, notify: ps.notify, }).then((x) => Antennas.findOneByOrFail(x.identifiers[0])); - + publishInternalEvent("antennaCreated", antenna); + await updateAntennaCache(); return await Antennas.pack(antenna); }); diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index e5a372f193..64ba1c2e1f 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -2,6 +2,7 @@ import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; import { Antennas } from "@/models/index.js"; import { publishInternalEvent } from "@/services/stream.js"; +import { updateAntennaCache } from "backend-rs"; export const meta = { tags: ["antennas"], @@ -40,4 +41,5 @@ export default define(meta, paramDef, async (ps, user) => { await Antennas.delete(antenna.id); publishInternalEvent("antennaDeleted", antenna); + await updateAntennaCache(); }); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 5e74f4d6b2..24d659f47a 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -2,6 +2,7 @@ import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js"; import { publishInternalEvent } from "@/services/stream.js"; +import { updateAntennaCache } from "backend-rs"; export const meta = { tags: ["antennas"], @@ -166,6 +167,7 @@ export default define(meta, paramDef, async (ps, user) => { "antennaUpdated", await Antennas.findOneByOrFail({ id: antenna.id }), ); + await updateAntennaCache(); return await Antennas.pack(antenna.id); });