From 9796d1791c3f7af4dc41f2fd52c86a1c29f908c7 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 5 Jun 2024 06:06:57 +0000
Subject: [PATCH 01/25] locale: update translations (English)

Currently translated at 99.9% (1944 of 1945 strings)

Translation: Firefish/locales
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/en/
---
 locales/en-US.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 231de35035..98cbca5e1f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -542,7 +542,7 @@ existingAccount: "Existing account"
 regenerate: "Regenerate"
 fontSize: "Font size"
 noFollowRequests: "You don't have any pending follow requests"
-noSentFollowRequests: "You don't have any sent follow requests"
+noSentFollowRequests: "You haven't sent any follow requests"
 openImageInNewTab: "Open images in new tab"
 dashboard: "Dashboard"
 local: "Local"
@@ -1580,7 +1580,7 @@ _ago:
   future: "future"
   justNow: "just now"
   secondsAgo: "{n}s ago"
-  minutesAgo: "{n}m ago"
+  minutesAgo: "{n}min ago"
   hoursAgo: "{n}h ago"
   daysAgo: "{n}d ago"
   weeksAgo: "{n}w ago"
@@ -1590,7 +1590,7 @@ _later:
   future: "future"
   justNow: "right now"
   secondsAgo: "in {n}s"
-  minutesAgo: "in {n}m"
+  minutesAgo: "in {n}min"
   hoursAgo: "in {n}h"
   daysAgo: "in {n}d"
   weeksAgo: "in {n}w"

From b111e6395333c2acaa22b4013bbb41f3b9b72ceb Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:35 +0900
Subject: [PATCH 02/25] chore (backend-rs): prefer unwrap_or_else

---
 packages/backend-rs/src/config/server.rs                | 2 +-
 packages/backend-rs/src/federation/nodeinfo/generate.rs | 4 ++--
 packages/backend-rs/src/init/system_info.rs             | 8 ++++----
 packages/backend-rs/src/service/antenna/check_hit.rs    | 2 +-
 packages/backend-rs/src/service/stream.rs               | 2 +-
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index e8a7cf73ab..41ada46a4e 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -293,7 +293,7 @@ pub fn load_config() -> Config {
     } else {
         server_config.redis.prefix.clone()
     }
-    .unwrap_or(hostname.clone());
+    .unwrap_or_else(|| hostname.clone());
 
     Config {
         url: server_config.url,
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index 49c3f8f63f..cc2add0fc4 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -66,7 +66,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
     let metadata = HashMap::from([
         (
             "nodeName".to_string(),
-            json!(meta.name.unwrap_or(CONFIG.host.clone())),
+            json!(meta.name.unwrap_or_else(|| CONFIG.host.clone())),
         ),
         ("nodeDescription".to_string(), json!(meta.description)),
         ("repositoryUrl".to_string(), json!(meta.repository_url)),
@@ -93,7 +93,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
         ("proxyAccountName".to_string(), json!(meta.proxy_account_id)),
         (
             "themeColor".to_string(),
-            json!(meta.theme_color.unwrap_or("#31748f".to_string())),
+            json!(meta.theme_color.unwrap_or_else(|| "#31748f".to_string())),
         ),
     ]);
 
diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs
index 138e7486e0..c4847cdd0d 100644
--- a/packages/backend-rs/src/init/system_info.rs
+++ b/packages/backend-rs/src/init/system_info.rs
@@ -26,19 +26,19 @@ pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
 
     tracing::info!(
         "Hostname: {}",
-        System::host_name().unwrap_or("unknown".to_string())
+        System::host_name().unwrap_or_else(|| "unknown".to_string())
     );
     tracing::info!(
         "OS: {}",
-        System::long_os_version().unwrap_or("unknown".to_string())
+        System::long_os_version().unwrap_or_else(|| "unknown".to_string())
     );
     tracing::info!(
         "Kernel: {}",
-        System::kernel_version().unwrap_or("unknown".to_string())
+        System::kernel_version().unwrap_or_else(|| "unknown".to_string())
     );
     tracing::info!(
         "CPU architecture: {}",
-        System::cpu_arch().unwrap_or("unknown".to_string())
+        System::cpu_arch().unwrap_or_else(|| "unknown".to_string())
     );
     tracing::info!("CPU threads: {}", system_info.cpus().len());
     tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576);
diff --git a/packages/backend-rs/src/service/antenna/check_hit.rs b/packages/backend-rs/src/service/antenna/check_hit.rs
index 8a0bec6e0a..cf4429212c 100644
--- a/packages/backend-rs/src/service/antenna/check_hit.rs
+++ b/packages/backend-rs/src/service/antenna/check_hit.rs
@@ -61,7 +61,7 @@ pub async fn check_hit_antenna(
                 == note_author
                     .host
                     .clone()
-                    .unwrap_or(CONFIG.host.clone())
+                    .unwrap_or_else(|| CONFIG.host.clone())
                     .to_ascii_lowercase()
         });
 
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index dc73499968..b87ded9261 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -66,7 +66,7 @@ pub async fn publish_to_stream(
         format!(
             "{{\"type\":\"{}\",\"body\":{}}}",
             kind,
-            value.unwrap_or("null".to_string()),
+            value.unwrap_or_else(|| "null".to_string()),
         )
     } else {
         value.ok_or(Error::Value("Invalid streaming message".to_string()))?

From fc590c14920ea8e8755eb19ccc4c2ceb756ae1d5 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:38 +0900
Subject: [PATCH 03/25] chore (backend-rs): use ws or wss for WebSocket

---
 packages/backend-rs/src/config/server.rs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 41ada46a4e..93169fe14c 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -278,7 +278,10 @@ pub fn load_config() -> Config {
         None => hostname.clone(),
     };
     let scheme = url.scheme().to_owned();
-    let ws_scheme = scheme.replace("http", "ws");
+    let ws_scheme = match scheme.as_str() {
+        "http" => "ws",
+        _ => "wss",
+    };
 
     let cluster_limits = match server_config.cluster_limits {
         Some(cl) => WorkerConfig {
@@ -344,7 +347,7 @@ pub fn load_config() -> Config {
         hostname,
         redis_key_prefix,
         scheme,
-        ws_scheme,
+        ws_scheme: ws_scheme.to_string(),
     }
 }
 

From e9d31687f92fc6a37ed804b709141ca8a60d66e5 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:38 +0900
Subject: [PATCH 04/25] chore (backend-rs): remove unused derive

---
 packages/backend-rs/src/config/server.rs      | 22 +++++++-------
 packages/backend-rs/src/federation/acct.rs    |  2 +-
 .../src/federation/nodeinfo/fetch.rs          |  4 +--
 .../src/federation/nodeinfo/schema.rs         | 30 ++++++++++++-------
 .../backend-rs/src/misc/get_image_size.rs     |  2 +-
 .../backend-rs/src/misc/get_note_summary.rs   |  2 +-
 packages/backend-rs/src/misc/reaction.rs      |  2 +-
 .../src/service/antenna/process_new_note.rs   |  5 ++--
 8 files changed, 39 insertions(+), 30 deletions(-)

diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 93169fe14c..101c860599 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -7,7 +7,7 @@ use std::fs;
 
 pub const VERSION: &str = macro_rs::read_version_from_package_json!();
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 struct ServerConfig {
@@ -72,7 +72,7 @@ struct ServerConfig {
     pub object_storage: Option<ObjectStorageConfig>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct DbConfig {
@@ -85,7 +85,7 @@ pub struct DbConfig {
     pub extra: Option<serde_json::Value>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct RedisConfig {
@@ -100,7 +100,7 @@ pub struct RedisConfig {
     pub prefix: Option<String>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct TlsConfig {
@@ -114,7 +114,7 @@ pub struct WorkerConfig {
     pub queue: u32,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct WorkerConfigInternal {
@@ -122,7 +122,7 @@ pub struct WorkerConfigInternal {
     pub queue: Option<u32>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct IdConfig {
@@ -130,7 +130,7 @@ pub struct IdConfig {
     pub fingerprint: Option<String>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct SysLogConfig {
@@ -138,7 +138,7 @@ pub struct SysLogConfig {
     pub port: u16,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct DeepLConfig {
@@ -147,7 +147,7 @@ pub struct DeepLConfig {
     pub is_pro: Option<bool>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct LibreTranslateConfig {
@@ -156,7 +156,7 @@ pub struct LibreTranslateConfig {
     pub api_key: Option<String>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct EmailConfig {
@@ -169,7 +169,7 @@ pub struct EmailConfig {
     pub use_implicit_ssl_tls: Option<bool>,
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
 pub struct ObjectStorageConfig {
diff --git a/packages/backend-rs/src/federation/acct.rs b/packages/backend-rs/src/federation/acct.rs
index 21e067c04e..b7b44356e6 100644
--- a/packages/backend-rs/src/federation/acct.rs
+++ b/packages/backend-rs/src/federation/acct.rs
@@ -1,7 +1,7 @@
 use std::fmt;
 use std::str::FromStr;
 
-#[derive(Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
 #[crate::export(object)]
 pub struct Acct {
     pub username: String,
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index 7c0058b92b..0147830815 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -25,13 +25,13 @@ pub enum Error {
 }
 
 /// Represents the schema of `/.well-known/nodeinfo`.
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize)]
 pub struct NodeinfoLinks {
     links: Vec<NodeinfoLink>,
 }
 
 /// Represents one entry of `/.well-known/nodeinfo`.
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize)]
 pub struct NodeinfoLink {
     rel: String,
     href: String,
diff --git a/packages/backend-rs/src/federation/nodeinfo/schema.rs b/packages/backend-rs/src/federation/nodeinfo/schema.rs
index abbe336723..2876afbbdb 100644
--- a/packages/backend-rs/src/federation/nodeinfo/schema.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/schema.rs
@@ -10,7 +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>
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct Nodeinfo21 {
     /// The schema version, must be 2.1.
@@ -30,7 +31,8 @@ pub struct Nodeinfo21 {
 }
 
 /// NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0>
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, js_name = "Nodeinfo")]
 pub struct Nodeinfo20 {
@@ -51,7 +53,8 @@ pub struct Nodeinfo20 {
 }
 
 /// Metadata about server software in use (version 2.1).
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct Software21 {
     /// The canonical name of this server software.
@@ -65,7 +68,8 @@ pub struct Software21 {
 }
 
 /// Metadata about server software in use (version 2.0).
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object)]
 pub struct Software20 {
@@ -75,7 +79,8 @@ pub struct Software20 {
     pub version: String,
 }
 
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
 #[crate::export(string_enum = "lowercase")]
 pub enum Protocol {
@@ -92,7 +97,8 @@ pub enum Protocol {
 }
 
 /// The third party sites this server can connect to via their application API.
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object)]
 pub struct Services {
@@ -103,7 +109,8 @@ pub struct Services {
 }
 
 /// The third party sites this server can retrieve messages from for combined display with regular traffic.
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
 #[crate::export(string_enum = "lowercase")]
 pub enum Inbound {
@@ -121,7 +128,8 @@ pub enum Inbound {
 }
 
 /// The third party sites this server can publish messages to on the behalf of a user.
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
 #[crate::export(string_enum = "lowercase")]
 pub enum Outbound {
@@ -158,7 +166,8 @@ pub enum Outbound {
 }
 
 /// Usage statistics for this server.
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object)]
 pub struct Usage {
@@ -168,7 +177,8 @@ pub struct Usage {
 }
 
 /// statistics about the users of this server.
-#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object)]
 pub struct Users {
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index c1348e62c3..5fc068fbe0 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -41,7 +41,7 @@ const BROWSER_SAFE_IMAGE_TYPES: [ImageFormat; 8] = [
 
 static MTX_GUARD: Mutex<()> = Mutex::const_new(());
 
-#[derive(Debug, PartialEq)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
 #[crate::export(object)]
 pub struct ImageSize {
     pub width: u32,
diff --git a/packages/backend-rs/src/misc/get_note_summary.rs b/packages/backend-rs/src/misc/get_note_summary.rs
index ab2cc88713..f2bfcd2470 100644
--- a/packages/backend-rs/src/misc/get_note_summary.rs
+++ b/packages/backend-rs/src/misc/get_note_summary.rs
@@ -1,6 +1,6 @@
 use serde::Deserialize;
 
-#[derive(Debug, Deserialize)]
+#[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object)]
 pub struct PartialNoteToSummarize {
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index 8e9800a166..50b679cd53 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -6,7 +6,7 @@ use regex::Regex;
 use sea_orm::prelude::*;
 use std::collections::HashMap;
 
-#[derive(PartialEq, Debug)]
+#[cfg_attr(test, derive(PartialEq, Debug))]
 #[crate::export(object)]
 pub struct DecodedReaction {
     pub reaction: String,
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 d28af982b6..a4fdc28848 100644
--- a/packages/backend-rs/src/service/antenna/process_new_note.rs
+++ b/packages/backend-rs/src/service/antenna/process_new_note.rs
@@ -28,15 +28,14 @@ pub enum Error {
 
 // for napi export
 // https://github.com/napi-rs/napi-rs/issues/2060
-type Antenna = antenna::Model;
 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<Vec<Antenna>, Error> {
+async fn antennas() -> Result<Vec<antenna::Model>, Error> {
     const CACHE_KEY: &str = "antennas";
 
-    if let Some(antennas) = cache::get::<Vec<Antenna>>(CACHE_KEY).await? {
+    if let Some(antennas) = cache::get::<Vec<antenna::Model>>(CACHE_KEY).await? {
         Ok(antennas)
     } else {
         let antennas = antenna::Entity::find().all(db_conn().await?).await?;

From f479ffb0e59dad54eadebb236bc864e71a76384d Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:38 +0900
Subject: [PATCH 05/25] chore (backend-rs): refactor storage_usage function

---
 packages/backend-rs/src/misc/system_info.rs | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs
index 95bd0dc490..2c4f6cdcf2 100644
--- a/packages/backend-rs/src/misc/system_info.rs
+++ b/packages/backend-rs/src/misc/system_info.rs
@@ -72,21 +72,17 @@ pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
 
 #[crate::export]
 pub fn storage_usage() -> Option<Storage> {
-    // Get the first disk that is actualy used.
+    // Get the first disk that is actualy used (has available space & has at least 1 GB total space).
     let disks = Disks::new_with_refreshed_list();
     let disk = disks
         .iter()
-        .find(|disk| disk.available_space() > 0 && disk.total_space() > disk.available_space());
+        .find(|disk| disk.available_space() > 0 && disk.total_space() > 1024 * 1024 * 1024)?;
 
-    if let Some(disk) = disk {
-        let total = disk.total_space() as i64;
-        let available = disk.available_space() as i64;
-        return Some(Storage {
-            total,
-            used: total - available,
-        });
-    }
+    let total = disk.total_space() as i64;
+    let available = disk.available_space() as i64;
 
-    tracing::debug!("failed to get stats");
-    None
+    Some(Storage {
+        total,
+        used: total - available,
+    })
 }

From 642c4cb2c7be68f0a1467ed989d39dd26fe45974 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 06/25] refactor (backend-rs): remove strum derives

---
 Cargo.lock                                    | 31 +-----
 Cargo.toml                                    |  1 -
 packages/backend-rs/Cargo.toml                |  1 -
 packages/backend-rs/index.d.ts                | 14 +--
 packages/backend-rs/src/database/cache.rs     | 16 +--
 .../src/service/push_notification.rs          | 56 ++++++-----
 packages/backend-rs/src/service/stream.rs     | 98 ++++++++++---------
 .../backend-rs/src/service/stream/antenna.rs  |  2 +-
 .../backend-rs/src/service/stream/channel.rs  |  2 +-
 .../backend-rs/src/service/stream/chat.rs     | 14 +--
 .../src/service/stream/chat_index.rs          | 10 +-
 .../src/service/stream/custom_emoji.rs        |  2 +-
 .../src/service/stream/group_chat.rs          |  9 +-
 .../src/service/stream/moderation.rs          |  2 +-
 14 files changed, 124 insertions(+), 134 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index add1b1c0dc..4eefdd76b4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -226,7 +226,6 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_yaml",
- "strum 0.26.2",
  "sysinfo",
  "thiserror",
  "tokio",
@@ -2644,12 +2643,6 @@ dependencies = [
  "untrusted",
 ]
 
-[[package]]
-name = "rustversion"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
-
 [[package]]
 name = "ryu"
 version = "1.0.18"
@@ -2712,7 +2705,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sqlx",
- "strum 0.25.0",
+ "strum",
  "thiserror",
  "time",
  "tracing",
@@ -3240,28 +3233,6 @@ version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
 
-[[package]]
-name = "strum"
-version = "0.26.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
-dependencies = [
- "strum_macros",
-]
-
-[[package]]
-name = "strum_macros"
-version = "0.26.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
-dependencies = [
- "heck 0.4.1",
- "proc-macro2",
- "quote",
- "rustversion",
- "syn 2.0.66",
-]
-
 [[package]]
 name = "subtle"
 version = "2.5.0"
diff --git a/Cargo.toml b/Cargo.toml
index a75cfc0190..ce4f502ba5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,7 +35,6 @@ sea-orm = { version = "0.12.15", default-features = false }
 serde = { version = "1.0.203", default-features = false }
 serde_json = { version = "1.0.117", default-features = false }
 serde_yaml = { version = "0.9.34", default-features = false }
-strum = { version = "0.26.2", default-features = false }
 syn = { version = "2.0.66", default-features = false }
 sysinfo = { version = "0.30.12", default-features = false }
 thiserror = { version = "1.0.61", default-features = false }
diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml
index d4942cd61c..a9bcb3777f 100644
--- a/packages/backend-rs/Cargo.toml
+++ b/packages/backend-rs/Cargo.toml
@@ -39,7 +39,6 @@ sea-orm = { workspace = true, features = ["macros", "runtime-tokio-rustls", "sql
 serde = { workspace = true, features = ["derive"] }
 serde_json = { workspace = true }
 serde_yaml = { workspace = true }
-strum = { workspace = true, features = ["derive"] }
 sysinfo = { workspace = true }
 thiserror = { workspace = true }
 tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index f9cb459f27..ace0f4fb52 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -1365,13 +1365,13 @@ export function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, noteMutedU
 export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise<void>
 export function unwatchNote(watcherId: string, noteId: string): Promise<void>
 export enum PushNotificationKind {
-  Generic = 'generic',
-  Chat = 'chat',
-  ReadAllChats = 'readAllChats',
-  ReadAllChatsInTheRoom = 'readAllChatsInTheRoom',
-  ReadNotifications = 'readNotifications',
-  ReadAllNotifications = 'readAllNotifications',
-  Mastodon = 'mastodon'
+  Generic = 0,
+  Chat = 1,
+  ReadAllChats = 2,
+  ReadAllChatsInTheRoom = 3,
+  ReadNotifications = 4,
+  ReadAllNotifications = 5,
+  Mastodon = 6
 }
 export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
 export function publishToChannelStream(channelId: string, userId: string): Promise<void>
diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs
index a5dd62a75e..c49806afdf 100644
--- a/packages/backend-rs/src/database/cache.rs
+++ b/packages/backend-rs/src/database/cache.rs
@@ -4,16 +4,12 @@ use crate::database::{redis_conn, redis_key, RedisConnError};
 use redis::{AsyncCommands, RedisError};
 use serde::{Deserialize, Serialize};
 
-#[derive(strum::Display, Debug)]
+#[cfg_attr(test, derive(Debug))]
 pub enum Category {
-    #[strum(serialize = "fetchUrl")]
     FetchUrl,
-    #[strum(serialize = "blocking")]
     Block,
-    #[strum(serialize = "following")]
     Follow,
     #[cfg(test)]
-    #[strum(serialize = "usedOnlyForTesting")]
     Test,
 }
 
@@ -32,9 +28,15 @@ fn prefix_key(key: &str) -> String {
     redis_key(format!("cache:{}", key))
 }
 
-#[inline]
 fn categorize(category: Category, key: &str) -> String {
-    format!("{}:{}", category, key)
+    let prefix = match category {
+        Category::FetchUrl => "fetchUrl",
+        Category::Block => "blocking",
+        Category::Follow => "following",
+        #[cfg(test)]
+        Category::Test => "usedOnlyForTesting",
+    };
+    format!("{}:{}", prefix, key)
 }
 
 #[inline]
diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs
index 9d19aceab1..b417630194 100644
--- a/packages/backend-rs/src/service/push_notification.rs
+++ b/packages/backend-rs/src/service/push_notification.rs
@@ -32,32 +32,18 @@ fn get_client() -> Result<IsahcWebPushClient, Error> {
         .cloned()?)
 }
 
-#[derive(strum::Display, PartialEq)]
-#[crate::export(string_enum = "camelCase")]
+#[crate::export]
 pub enum PushNotificationKind {
-    #[strum(serialize = "notification")]
     Generic,
-    #[strum(serialize = "unreadMessagingMessage")]
     Chat,
-    #[strum(serialize = "readAllMessagingMessages")]
     ReadAllChats,
-    #[strum(serialize = "readAllMessagingMessagesOfARoom")]
     ReadAllChatsInTheRoom,
-    #[strum(serialize = "readNotifications")]
     ReadNotifications,
-    #[strum(serialize = "readAllNotifications")]
     ReadAllNotifications,
     Mastodon,
 }
 
-fn compact_content(
-    kind: &PushNotificationKind,
-    mut content: serde_json::Value,
-) -> Result<serde_json::Value, Error> {
-    if kind != &PushNotificationKind::Generic {
-        return Ok(content);
-    }
-
+fn compact_content(mut content: serde_json::Value) -> Result<serde_json::Value, Error> {
     if !content.is_object() {
         return Err(Error::InvalidContent("not a JSON object".to_string()));
     }
@@ -159,24 +145,40 @@ pub async fn send_push_notification(
         .all(db)
         .await?;
 
+    let use_mastodon_api = matches!(kind, PushNotificationKind::Mastodon);
+
     // TODO: refactoring
-    let payload = if kind == PushNotificationKind::Mastodon {
+    let payload = if use_mastodon_api {
         // Leave the `content` as it is
         serde_json::to_string(content)?
     } else {
         // Format the `content` passed from the TypeScript backend
         // for Firefish push notifications
+        let label = match kind {
+            PushNotificationKind::Generic => "notification",
+            PushNotificationKind::Chat => "unreadMessagingMessage",
+            PushNotificationKind::ReadAllChats => "readAllMessagingMessages",
+            PushNotificationKind::ReadAllChatsInTheRoom => "readAllMessagingMessagesOfARoom",
+            PushNotificationKind::ReadNotifications => "readNotifications",
+            PushNotificationKind::ReadAllNotifications => "readAllNotifications",
+            // unreachable
+            _ => "unknown",
+        };
         format!(
             "{{\"type\":\"{}\",\"userId\":\"{}\",\"dateTime\":{},\"body\":{}}}",
-            kind,
+            label,
             receiver_user_id,
             chrono::Utc::now().timestamp_millis(),
-            serde_json::to_string(&compact_content(&kind, content.clone())?)?
+            match kind {
+                PushNotificationKind::Generic =>
+                    serde_json::to_string(&compact_content(content.to_owned())?)?,
+                _ => serde_json::to_string(&content)?,
+            }
         )
     };
     tracing::trace!("payload: {}", payload);
 
-    let encoding = if kind == PushNotificationKind::Mastodon {
+    let encoding = if use_mastodon_api {
         ContentEncoding::AesGcm
     } else {
         ContentEncoding::Aes128Gcm
@@ -184,13 +186,13 @@ pub async fn send_push_notification(
 
     for subscription in subscriptions.iter() {
         if !subscription.send_read_message
-            && [
-                PushNotificationKind::ReadAllChats,
-                PushNotificationKind::ReadAllChatsInTheRoom,
-                PushNotificationKind::ReadAllNotifications,
-                PushNotificationKind::ReadNotifications,
-            ]
-            .contains(&kind)
+            && matches!(
+                kind,
+                PushNotificationKind::ReadAllChats
+                    | PushNotificationKind::ReadAllChatsInTheRoom
+                    | PushNotificationKind::ReadAllNotifications
+                    | PushNotificationKind::ReadNotifications
+            )
         {
             continue;
         }
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index b87ded9261..47cff7de1f 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -10,39 +10,44 @@ use crate::config::CONFIG;
 use crate::database::{redis_conn, RedisConnError};
 use redis::{AsyncCommands, RedisError};
 
-#[derive(strum::Display)]
 pub enum Stream {
-    #[strum(serialize = "internal")]
     Internal,
-    #[strum(serialize = "broadcast")]
     CustomEmoji,
-    #[strum(to_string = "adminStream:{moderator_id}")]
-    Moderation { moderator_id: String },
-    #[strum(to_string = "user:{user_id}")]
-    User { user_id: String },
-    #[strum(to_string = "channelStream:{channel_id}")]
-    Channel { channel_id: String },
-    #[strum(to_string = "noteStream:{note_id}")]
-    Note { note_id: String },
-    #[strum(serialize = "notesStream")]
+    Moderation {
+        moderator_id: String,
+    },
+    User {
+        user_id: String,
+    },
+    Channel {
+        channel_id: String,
+    },
+    Note {
+        note_id: String,
+    },
     Notes,
-    #[strum(to_string = "userListStream:{list_id}")]
-    UserList { list_id: String },
-    #[strum(to_string = "mainStream:{user_id}")]
-    Main { user_id: String },
-    #[strum(to_string = "driveStream:{user_id}")]
-    Drive { user_id: String },
-    #[strum(to_string = "antennaStream:{antenna_id}")]
-    Antenna { antenna_id: String },
-    #[strum(to_string = "messagingStream:{sender_user_id}-{receiver_user_id}")]
+    UserList {
+        list_id: String,
+    },
+    Main {
+        user_id: String,
+    },
+    Drive {
+        user_id: String,
+    },
+    Antenna {
+        antenna_id: String,
+    },
     Chat {
         sender_user_id: String,
         receiver_user_id: String,
     },
-    #[strum(to_string = "messagingStream:{group_id}")]
-    GroupChat { group_id: String },
-    #[strum(to_string = "messagingIndexStream:{user_id}")]
-    ChatIndex { user_id: String },
+    GroupChat {
+        group_id: String,
+    },
+    ChatIndex {
+        user_id: String,
+    },
 }
 
 #[derive(thiserror::Error, Debug)]
@@ -59,9 +64,29 @@ pub enum Error {
 
 pub async fn publish_to_stream(
     stream: &Stream,
-    kind: Option<String>,
+    kind: Option<&str>,
     value: Option<String>,
 ) -> Result<(), Error> {
+    let channel = match stream {
+        Stream::Internal => "internal".to_string(),
+        Stream::CustomEmoji => "broadcast".to_string(),
+        Stream::Moderation { moderator_id } => format!("adminStream:{moderator_id}"),
+        Stream::User { user_id } => format!("user:{user_id}"),
+        Stream::Channel { channel_id } => format!("channelStream:{channel_id}"),
+        Stream::Note { note_id } => format!("noteStream:{note_id}"),
+        Stream::Notes => "notesStream".to_string(),
+        Stream::UserList { list_id } => format!("userListStream:{list_id}"),
+        Stream::Main { user_id } => format!("mainStream:{user_id}"),
+        Stream::Drive { user_id } => format!("driveStream:{user_id}"),
+        Stream::Antenna { antenna_id } => format!("antennaStream:{antenna_id}"),
+        Stream::Chat {
+            sender_user_id,
+            receiver_user_id,
+        } => format!("messagingStream:{sender_user_id}-{receiver_user_id}"),
+        Stream::GroupChat { group_id } => format!("messagingStream:{group_id}"),
+        Stream::ChatIndex { user_id } => format!("messagingIndexStream:{user_id}"),
+    };
+
     let message = if let Some(kind) = kind {
         format!(
             "{{\"type\":\"{}\",\"body\":{}}}",
@@ -76,28 +101,9 @@ pub async fn publish_to_stream(
         .await?
         .publish(
             &CONFIG.host,
-            format!("{{\"channel\":\"{}\",\"message\":{}}}", stream, message),
+            format!("{{\"channel\":\"{}\",\"message\":{}}}", channel, message),
         )
         .await?;
 
     Ok(())
 }
-
-#[cfg(test)]
-mod unit_test {
-    use super::Stream;
-    use pretty_assertions::assert_eq;
-
-    #[test]
-    fn channel_to_string() {
-        assert_eq!(Stream::Internal.to_string(), "internal");
-        assert_eq!(Stream::CustomEmoji.to_string(), "broadcast");
-        assert_eq!(
-            Stream::Moderation {
-                moderator_id: "9tb42br63g5apjcq".to_string()
-            }
-            .to_string(),
-            "adminStream:9tb42br63g5apjcq"
-        );
-    }
-}
diff --git a/packages/backend-rs/src/service/stream/antenna.rs b/packages/backend-rs/src/service/stream/antenna.rs
index 3058d9f04c..7210719c93 100644
--- a/packages/backend-rs/src/service/stream/antenna.rs
+++ b/packages/backend-rs/src/service/stream/antenna.rs
@@ -4,7 +4,7 @@ use crate::service::stream::{publish_to_stream, Error, Stream};
 pub async fn publish(antenna_id: String, note: &note::Model) -> Result<(), Error> {
     publish_to_stream(
         &Stream::Antenna { antenna_id },
-        Some("note".to_string()),
+        Some("note"),
         Some(serde_json::to_string(note)?),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/channel.rs b/packages/backend-rs/src/service/stream/channel.rs
index 9f5cf3802a..028aab69c7 100644
--- a/packages/backend-rs/src/service/stream/channel.rs
+++ b/packages/backend-rs/src/service/stream/channel.rs
@@ -4,7 +4,7 @@ use crate::service::stream::{publish_to_stream, Error, Stream};
 pub async fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
     publish_to_stream(
         &Stream::Channel { channel_id },
-        Some("typing".to_string()),
+        Some("typing"),
         Some(format!("\"{}\"", user_id)),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/chat.rs b/packages/backend-rs/src/service/stream/chat.rs
index 84280c319c..62701d3976 100644
--- a/packages/backend-rs/src/service/stream/chat.rs
+++ b/packages/backend-rs/src/service/stream/chat.rs
@@ -1,15 +1,10 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[derive(strum::Display)]
 #[crate::export(string_enum = "camelCase")]
 pub enum ChatEvent {
-    #[strum(serialize = "message")]
     Message,
-    #[strum(serialize = "read")]
     Read,
-    #[strum(serialize = "deleted")]
     Deleted,
-    #[strum(serialize = "typing")]
     Typing,
 }
 
@@ -23,12 +18,19 @@ pub async fn publish(
     kind: ChatEvent,
     object: &serde_json::Value,
 ) -> Result<(), Error> {
+    let kind = match kind {
+        ChatEvent::Message => "message",
+        ChatEvent::Read => "read",
+        ChatEvent::Deleted => "deleted",
+        ChatEvent::Typing => "typing",
+    };
+
     publish_to_stream(
         &Stream::Chat {
             sender_user_id,
             receiver_user_id,
         },
-        Some(kind.to_string()),
+        Some(kind),
         Some(serde_json::to_string(object)?),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/chat_index.rs b/packages/backend-rs/src/service/stream/chat_index.rs
index 6619c5589c..790dd905ea 100644
--- a/packages/backend-rs/src/service/stream/chat_index.rs
+++ b/packages/backend-rs/src/service/stream/chat_index.rs
@@ -1,11 +1,8 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[derive(strum::Display)]
 #[crate::export(string_enum = "camelCase")]
 pub enum ChatIndexEvent {
-    #[strum(serialize = "message")]
     Message,
-    #[strum(serialize = "read")]
     Read,
 }
 
@@ -18,9 +15,14 @@ pub async fn publish(
     kind: ChatIndexEvent,
     object: &serde_json::Value,
 ) -> Result<(), Error> {
+    let kind = match kind {
+        ChatIndexEvent::Message => "message",
+        ChatIndexEvent::Read => "read",
+    };
+
     publish_to_stream(
         &Stream::ChatIndex { user_id },
-        Some(kind.to_string()),
+        Some(kind),
         Some(serde_json::to_string(object)?),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/custom_emoji.rs b/packages/backend-rs/src/service/stream/custom_emoji.rs
index 29b655b6e6..164d89b957 100644
--- a/packages/backend-rs/src/service/stream/custom_emoji.rs
+++ b/packages/backend-rs/src/service/stream/custom_emoji.rs
@@ -21,7 +21,7 @@ pub struct PackedEmoji {
 pub async fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
     publish_to_stream(
         &Stream::CustomEmoji,
-        Some("emojiAdded".to_string()),
+        Some("emojiAdded"),
         Some(format!("{{\"emoji\":{}}}", serde_json::to_string(emoji)?)),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/group_chat.rs b/packages/backend-rs/src/service/stream/group_chat.rs
index 20c04c6fa2..6cf3c5f671 100644
--- a/packages/backend-rs/src/service/stream/group_chat.rs
+++ b/packages/backend-rs/src/service/stream/group_chat.rs
@@ -9,9 +9,16 @@ pub async fn publish(
     kind: ChatEvent,
     object: &serde_json::Value,
 ) -> Result<(), Error> {
+    let kind = match kind {
+        ChatEvent::Message => "message",
+        ChatEvent::Read => "read",
+        ChatEvent::Deleted => "deleted",
+        ChatEvent::Typing => "typing",
+    };
+
     publish_to_stream(
         &Stream::GroupChat { group_id },
-        Some(kind.to_string()),
+        Some(kind),
         Some(serde_json::to_string(object)?),
     )
     .await
diff --git a/packages/backend-rs/src/service/stream/moderation.rs b/packages/backend-rs/src/service/stream/moderation.rs
index e9e17d2399..218441e15f 100644
--- a/packages/backend-rs/src/service/stream/moderation.rs
+++ b/packages/backend-rs/src/service/stream/moderation.rs
@@ -15,7 +15,7 @@ pub struct AbuseUserReportLike {
 pub async fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
     publish_to_stream(
         &Stream::Moderation { moderator_id },
-        Some("newAbuseUserReport".to_string()),
+        Some("newAbuseUserReport"),
         Some(serde_json::to_string(report)?),
     )
     .await

From 97765209a265734ddc2eaa02eeb86caabd9476ba Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 07/25] chore (backend-rs): organize imports

---
 packages/backend-rs/index.d.ts                  | 16 ++++++++--------
 packages/backend-rs/index.js                    |  4 ++--
 packages/backend-rs/src/config/meta.rs          |  3 +--
 packages/backend-rs/src/config/server.rs        |  3 +--
 packages/backend-rs/src/federation/acct.rs      |  3 +--
 .../backend-rs/src/federation/nodeinfo/fetch.rs |  3 +--
 .../src/federation/nodeinfo/generate.rs         | 12 +++++++-----
 packages/backend-rs/src/misc/get_image_size.rs  |  3 +--
 .../backend-rs/src/misc/get_note_all_texts.rs   |  6 ++++--
 packages/backend-rs/src/misc/latest_version.rs  |  3 +--
 packages/backend-rs/src/misc/reaction.rs        |  4 +---
 .../misc/remove_old_attestation_challenges.rs   |  3 +--
 .../backend-rs/src/service/antenna/check_hit.rs | 10 ++++++----
 .../src/service/antenna/process_new_note.rs     | 16 +++++++++-------
 packages/backend-rs/src/service/note/watch.rs   |  4 +---
 .../backend-rs/src/service/push_notification.rs | 17 ++++++++---------
 packages/backend-rs/src/service/stream.rs       | 14 ++++++++++++--
 .../backend-rs/src/service/stream/antenna.rs    |  6 ++++--
 packages/backend-rs/src/service/stream/chat.rs  | 10 +---------
 .../backend-rs/src/service/stream/chat_index.rs |  2 +-
 .../backend-rs/src/service/stream/group_chat.rs |  2 +-
 21 files changed, 72 insertions(+), 72 deletions(-)

diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index ace0f4fb52..56f3cfe49e 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -1375,16 +1375,10 @@ export enum PushNotificationKind {
 }
 export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
 export function publishToChannelStream(channelId: string, userId: string): Promise<void>
-export enum ChatEvent {
-  Message = 'message',
-  Read = 'read',
-  Deleted = 'deleted',
-  Typing = 'typing'
-}
 export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): Promise<void>
 export enum ChatIndexEvent {
-  Message = 'message',
-  Read = 'read'
+  Message = 0,
+  Read = 1
 }
 export function publishToChatIndexStream(userId: string, kind: ChatIndexEvent, object: any): Promise<void>
 export interface PackedEmoji {
@@ -1407,6 +1401,12 @@ export interface AbuseUserReportLike {
   comment: string
 }
 export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): Promise<void>
+export enum ChatEvent {
+  Message = 0,
+  Read = 1,
+  Deleted = 2,
+  Typing = 3
+}
 export function getTimestamp(id: string): number
 /**
  * The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 5f16e2febd..b3d84eceaf 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, loadEnv, 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, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
+const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, 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
 
 module.exports.SECOND = SECOND
 module.exports.MINUTE = MINUTE
@@ -384,13 +384,13 @@ module.exports.unwatchNote = unwatchNote
 module.exports.PushNotificationKind = PushNotificationKind
 module.exports.sendPushNotification = sendPushNotification
 module.exports.publishToChannelStream = publishToChannelStream
-module.exports.ChatEvent = ChatEvent
 module.exports.publishToChatStream = publishToChatStream
 module.exports.ChatIndexEvent = ChatIndexEvent
 module.exports.publishToChatIndexStream = publishToChatIndexStream
 module.exports.publishToBroadcastStream = publishToBroadcastStream
 module.exports.publishToGroupChatStream = publishToGroupChatStream
 module.exports.publishToModerationStream = publishToModerationStream
+module.exports.ChatEvent = ChatEvent
 module.exports.getTimestamp = getTimestamp
 module.exports.genId = genId
 module.exports.genIdAt = genIdAt
diff --git a/packages/backend-rs/src/config/meta.rs b/packages/backend-rs/src/config/meta.rs
index 9a81dc870c..cedb6cf0ad 100644
--- a/packages/backend-rs/src/config/meta.rs
+++ b/packages/backend-rs/src/config/meta.rs
@@ -1,7 +1,6 @@
 //! Server information
 
-use crate::database::db_conn;
-use crate::model::entity::meta;
+use crate::{database::db_conn, model::entity::meta};
 use sea_orm::{prelude::*, ActiveValue};
 use std::sync::Mutex;
 
diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 101c860599..5170b0617e 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -2,8 +2,7 @@
 
 use once_cell::sync::Lazy;
 use serde::Deserialize;
-use std::env;
-use std::fs;
+use std::{env, fs};
 
 pub const VERSION: &str = macro_rs::read_version_from_package_json!();
 
diff --git a/packages/backend-rs/src/federation/acct.rs b/packages/backend-rs/src/federation/acct.rs
index b7b44356e6..eb49e0e45b 100644
--- a/packages/backend-rs/src/federation/acct.rs
+++ b/packages/backend-rs/src/federation/acct.rs
@@ -1,5 +1,4 @@
-use std::fmt;
-use std::str::FromStr;
+use std::{fmt, str::FromStr};
 
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[crate::export(object)]
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index 0147830815..9095665334 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -2,8 +2,7 @@
 //!
 //! ref: <https://nodeinfo.diaspora.software/protocol.html>
 
-use crate::federation::nodeinfo::schema::*;
-use crate::util::http_client;
+use crate::{federation::nodeinfo::schema::*, util::http_client};
 use isahc::AsyncReadResponseExt;
 use serde::Deserialize;
 
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index cc2add0fc4..63621e9e92 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -1,10 +1,12 @@
 //! NodeInfo generator
 
-use crate::config::{local_server_info, CONFIG};
-use crate::database::{cache, db_conn};
-use crate::federation::nodeinfo::schema::*;
-use crate::model::entity::{note, user};
-use sea_orm::{ColumnTrait, DbErr, EntityTrait, PaginatorTrait, QueryFilter};
+use crate::{
+    config::{local_server_info, CONFIG},
+    database::{cache, db_conn},
+    federation::nodeinfo::schema::*,
+    model::entity::{note, user},
+};
+use sea_orm::prelude::*;
 use serde_json::json;
 use std::collections::HashMap;
 
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index 5fc068fbe0..bfe5c4edd3 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -1,5 +1,4 @@
-use crate::database::cache;
-use crate::util::http_client;
+use crate::{database::cache, util::http_client};
 use image::{io::Reader, ImageError, ImageFormat};
 use isahc::ReadResponseExt;
 use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag};
diff --git a/packages/backend-rs/src/misc/get_note_all_texts.rs b/packages/backend-rs/src/misc/get_note_all_texts.rs
index 7dc161665c..0532c48054 100644
--- a/packages/backend-rs/src/misc/get_note_all_texts.rs
+++ b/packages/backend-rs/src/misc/get_note_all_texts.rs
@@ -1,5 +1,7 @@
-use crate::database::db_conn;
-use crate::model::entity::{drive_file, note};
+use crate::{
+    database::db_conn,
+    model::entity::{drive_file, note},
+};
 use sea_orm::{prelude::*, QuerySelect};
 
 #[crate::export(object)]
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 7233814df6..1792adee06 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -1,7 +1,6 @@
 //! Fetch latest Firefish version from the Firefish repository
 
-use crate::database::cache;
-use crate::util::http_client;
+use crate::{database::cache, util::http_client};
 use isahc::ReadResponseExt;
 use serde::Deserialize;
 
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index 50b679cd53..4f11894ee7 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -1,6 +1,4 @@
-use crate::config::local_server_info;
-use crate::database::db_conn;
-use crate::model::entity::emoji;
+use crate::{config::local_server_info, database::db_conn, model::entity::emoji};
 use once_cell::sync::Lazy;
 use regex::Regex;
 use sea_orm::prelude::*;
diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
index 9102d1ce39..2dc45c02eb 100644
--- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
+++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
@@ -1,7 +1,6 @@
 // TODO: We want to get rid of this
 
-use crate::database::db_conn;
-use crate::model::entity::attestation_challenge;
+use crate::{database::db_conn, model::entity::attestation_challenge};
 use chrono::{Duration, Utc};
 use sea_orm::prelude::*;
 
diff --git a/packages/backend-rs/src/service/antenna/check_hit.rs b/packages/backend-rs/src/service/antenna/check_hit.rs
index cf4429212c..0f297ccbb2 100644
--- a/packages/backend-rs/src/service/antenna/check_hit.rs
+++ b/packages/backend-rs/src/service/antenna/check_hit.rs
@@ -1,7 +1,9 @@
-use crate::config::CONFIG;
-use crate::database::{cache, db_conn};
-use crate::federation::acct::Acct;
-use crate::model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*};
+use crate::{
+    config::CONFIG,
+    database::{cache, db_conn},
+    federation::acct::Acct,
+    model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*},
+};
 use sea_orm::{prelude::*, QuerySelect};
 
 #[derive(thiserror::Error, Debug)]
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 a4fdc28848..1af4e93433 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,12 @@
-use crate::database::{cache, db_conn, redis_conn, redis_key, RedisConnError};
-use crate::federation::acct::Acct;
-use crate::misc::get_note_all_texts::{all_texts, PartialNoteToElaborate};
-use crate::model::entity::{antenna, note};
-use crate::service::antenna::check_hit::{check_hit_antenna, AntennaCheckError};
-use crate::service::stream;
-use crate::util::id::{get_timestamp, InvalidIdError};
+use crate::{
+    database::{cache, db_conn, 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,
+    util::id::{get_timestamp, InvalidIdError},
+};
 use redis::{streams::StreamMaxlen, AsyncCommands, RedisError};
 use sea_orm::prelude::*;
 
diff --git a/packages/backend-rs/src/service/note/watch.rs b/packages/backend-rs/src/service/note/watch.rs
index d6496553e3..d63ce00e85 100644
--- a/packages/backend-rs/src/service/note/watch.rs
+++ b/packages/backend-rs/src/service/note/watch.rs
@@ -1,6 +1,4 @@
-use crate::database::db_conn;
-use crate::model::entity::note_watching;
-use crate::util::id::gen_id_at;
+use crate::{database::db_conn, model::entity::note_watching, util::id::gen_id_at};
 use sea_orm::{prelude::*, ActiveValue};
 
 #[crate::export]
diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs
index b417630194..35c97f86c1 100644
--- a/packages/backend-rs/src/service/push_notification.rs
+++ b/packages/backend-rs/src/service/push_notification.rs
@@ -1,14 +1,13 @@
-use crate::config::local_server_info;
-use crate::database::db_conn;
-use crate::misc::get_note_summary::{get_note_summary, PartialNoteToSummarize};
-use crate::model::entity::sw_subscription;
-use crate::util::http_client;
+use crate::{
+    config::local_server_info,
+    database::db_conn,
+    misc::get_note_summary::{get_note_summary, PartialNoteToSummarize},
+    model::entity::sw_subscription,
+    util::http_client,
+};
 use once_cell::sync::OnceCell;
 use sea_orm::prelude::*;
-use web_push::{
-    ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder,
-    WebPushClient, WebPushError, WebPushMessageBuilder,
-};
+use web_push::*;
 
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index 47cff7de1f..a028707d38 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -6,8 +6,10 @@ pub mod custom_emoji;
 pub mod group_chat;
 pub mod moderation;
 
-use crate::config::CONFIG;
-use crate::database::{redis_conn, RedisConnError};
+use crate::{
+    config::CONFIG,
+    database::{redis_conn, RedisConnError},
+};
 use redis::{AsyncCommands, RedisError};
 
 pub enum Stream {
@@ -50,6 +52,14 @@ pub enum Stream {
     },
 }
 
+#[crate::export]
+pub enum ChatEvent {
+    Message,
+    Read,
+    Deleted,
+    Typing,
+}
+
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
     #[error("Redis error: {0}")]
diff --git a/packages/backend-rs/src/service/stream/antenna.rs b/packages/backend-rs/src/service/stream/antenna.rs
index 7210719c93..ddd3dd711a 100644
--- a/packages/backend-rs/src/service/stream/antenna.rs
+++ b/packages/backend-rs/src/service/stream/antenna.rs
@@ -1,5 +1,7 @@
-use crate::model::entity::note;
-use crate::service::stream::{publish_to_stream, Error, Stream};
+use crate::{
+    model::entity::note,
+    service::stream::{publish_to_stream, Error, Stream},
+};
 
 pub async fn publish(antenna_id: String, note: &note::Model) -> Result<(), Error> {
     publish_to_stream(
diff --git a/packages/backend-rs/src/service/stream/chat.rs b/packages/backend-rs/src/service/stream/chat.rs
index 62701d3976..2f60ff4015 100644
--- a/packages/backend-rs/src/service/stream/chat.rs
+++ b/packages/backend-rs/src/service/stream/chat.rs
@@ -1,12 +1,4 @@
-use crate::service::stream::{publish_to_stream, Error, Stream};
-
-#[crate::export(string_enum = "camelCase")]
-pub enum ChatEvent {
-    Message,
-    Read,
-    Deleted,
-    Typing,
-}
+use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
 
 // We want to merge `kind` and `object` into a single enum
 // https://github.com/napi-rs/napi-rs/issues/2036
diff --git a/packages/backend-rs/src/service/stream/chat_index.rs b/packages/backend-rs/src/service/stream/chat_index.rs
index 790dd905ea..e686db8149 100644
--- a/packages/backend-rs/src/service/stream/chat_index.rs
+++ b/packages/backend-rs/src/service/stream/chat_index.rs
@@ -1,6 +1,6 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[crate::export(string_enum = "camelCase")]
+#[crate::export]
 pub enum ChatIndexEvent {
     Message,
     Read,
diff --git a/packages/backend-rs/src/service/stream/group_chat.rs b/packages/backend-rs/src/service/stream/group_chat.rs
index 6cf3c5f671..49ebc299d2 100644
--- a/packages/backend-rs/src/service/stream/group_chat.rs
+++ b/packages/backend-rs/src/service/stream/group_chat.rs
@@ -1,4 +1,4 @@
-use crate::service::stream::{chat::ChatEvent, publish_to_stream, Error, Stream};
+use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
 
 // We want to merge `kind` and `object` into a single enum
 // https://github.com/napi-rs/napi-rs/issues/2036

From 7e2493b25765400a53adf8fb1b33f7d248a1da7c Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 08/25] refactor (backend): drop support for MK- environment
 variables

---
 docs/notice-for-admins.md                     | 11 +++++++
 packages/backend-rs/index.d.ts                | 10 -------
 packages/backend-rs/index.js                  |  3 +-
 packages/backend-rs/src/config/environment.rs | 29 -------------------
 packages/backend-rs/src/config/mod.rs         |  1 -
 packages/backend/package.json                 |  1 -
 packages/backend/src/@types/koa-slow.d.ts     | 14 ---------
 packages/backend/src/boot/index.ts            |  5 ++--
 packages/backend/src/boot/master.ts           | 20 +++++--------
 packages/backend/src/config.ts                |  3 +-
 packages/backend/src/queue/index.ts           |  4 +--
 packages/backend/src/server/index.ts          | 12 +-------
 packages/backend/src/services/logger.ts       |  6 ++--
 pnpm-lock.yaml                                | 27 -----------------
 14 files changed, 27 insertions(+), 119 deletions(-)
 delete mode 100644 packages/backend-rs/src/config/environment.rs
 delete mode 100644 packages/backend/src/@types/koa-slow.d.ts

diff --git a/docs/notice-for-admins.md b/docs/notice-for-admins.md
index 085523c825..b65a91a773 100644
--- a/docs/notice-for-admins.md
+++ b/docs/notice-for-admins.md
@@ -2,6 +2,17 @@
 
 You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
 
+## Unreleased
+
+The following environment variables are deprecated and no longer have any effect:
+- `MK_ONLY_QUEUE`
+- `MK_ONLY_SERVER`
+- `MK_NO_DAEMONS`
+- `MK_DISABLE_CLUSTERING`
+- `MK_VERBOSE`
+- `MK_WITH_LOG_TIME`
+- `MK_SLOW`
+
 ## v20240601
 
 ### For systemd/pm2 users
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 56f3cfe49e..6b92608f7f 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -19,16 +19,6 @@ export const USER_ACTIVE_THRESHOLD: number
  * * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
  */
 export const FILE_TYPE_BROWSERSAFE: string[]
-export interface EnvConfig {
-  onlyQueue: boolean
-  onlyServer: boolean
-  noDaemons: boolean
-  disableClustering: boolean
-  verbose: boolean
-  withLogTime: boolean
-  slow: boolean
-}
-export function loadEnv(): EnvConfig
 export function fetchMeta(): Promise<Meta>
 export function updateMetaCache(): Promise<void>
 export interface PugArgs {
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index b3d84eceaf..ab79e101c8 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, loadEnv, 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, 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
@@ -319,7 +319,6 @@ module.exports.DAY = DAY
 module.exports.USER_ONLINE_THRESHOLD = USER_ONLINE_THRESHOLD
 module.exports.USER_ACTIVE_THRESHOLD = USER_ACTIVE_THRESHOLD
 module.exports.FILE_TYPE_BROWSERSAFE = FILE_TYPE_BROWSERSAFE
-module.exports.loadEnv = loadEnv
 module.exports.fetchMeta = fetchMeta
 module.exports.updateMetaCache = updateMetaCache
 module.exports.metaToPugArgs = metaToPugArgs
diff --git a/packages/backend-rs/src/config/environment.rs b/packages/backend-rs/src/config/environment.rs
deleted file mode 100644
index 8efe509c7e..0000000000
--- a/packages/backend-rs/src/config/environment.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-//! Environment options
-
-// FIXME: Are these options used?
-#[crate::export(object)]
-pub struct EnvConfig {
-    pub only_queue: bool,
-    pub only_server: bool,
-    pub no_daemons: bool,
-    pub disable_clustering: bool,
-    pub verbose: bool,
-    pub with_log_time: bool,
-    pub slow: bool,
-}
-
-#[crate::export]
-pub fn load_env() -> EnvConfig {
-    let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase();
-    let is_testing = node_env == "test";
-
-    EnvConfig {
-        only_queue: std::env::var("MK_ONLY_QUEUE").is_ok(),
-        only_server: std::env::var("MK_ONLY_SERVER").is_ok(),
-        no_daemons: is_testing || std::env::var("MK_NO_DAEMONS").is_ok(),
-        disable_clustering: is_testing || std::env::var("MK_DISABLE_CLUSTERING").is_ok(),
-        verbose: std::env::var("MK_VERBOSE").is_ok(),
-        with_log_time: std::env::var("MK_WITH_LOG_TIME").is_ok(),
-        slow: std::env::var("MK_SLOW").is_ok(),
-    }
-}
diff --git a/packages/backend-rs/src/config/mod.rs b/packages/backend-rs/src/config/mod.rs
index 483ca3ac6e..a1cfb7fd75 100644
--- a/packages/backend-rs/src/config/mod.rs
+++ b/packages/backend-rs/src/config/mod.rs
@@ -4,6 +4,5 @@ pub use meta::local_server_info;
 pub use server::CONFIG;
 
 pub mod constant;
-pub mod environment;
 pub mod meta;
 pub mod server;
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 70dab11f04..1e3c72576f 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -77,7 +77,6 @@
 		"koa-mount": "4.0.0",
 		"koa-remove-trailing-slashes": "2.0.3",
 		"koa-send": "5.0.1",
-		"koa-slow": "2.1.0",
 		"megalodon": "workspace:*",
 		"mfm-js": "0.24.0",
 		"mime-types": "2.1.35",
diff --git a/packages/backend/src/@types/koa-slow.d.ts b/packages/backend/src/@types/koa-slow.d.ts
deleted file mode 100644
index e24be51e2a..0000000000
--- a/packages/backend/src/@types/koa-slow.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-declare module "koa-slow" {
-	import type { Middleware } from "koa";
-
-	interface ISlowOptions {
-		url?: RegExp;
-		delay?: number;
-	}
-
-	function slow(options?: ISlowOptions): Middleware;
-
-	namespace slow {} // Hack
-
-	export = slow;
-}
diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts
index 245a80846e..1ebe0f4642 100644
--- a/packages/backend/src/boot/index.ts
+++ b/packages/backend/src/boot/index.ts
@@ -3,7 +3,6 @@ import chalk from "chalk";
 import Xev from "xev";
 
 import Logger from "@/services/logger.js";
-import { envOption } from "@/config.js";
 import { inspect } from "node:util";
 
 // for typeorm
@@ -31,14 +30,14 @@ export default async function () {
 	const type = cluster.isPrimary ? "(master)" : "(worker)";
 	process.title = `Firefish ${mode} ${type}`;
 
-	if (cluster.isPrimary || envOption.disableClustering) {
+	if (cluster.isPrimary) {
 		await masterMain();
 		if (cluster.isPrimary) {
 			ev.mount();
 		}
 	}
 
-	if (cluster.isWorker || envOption.disableClustering) {
+	if (cluster.isWorker) {
 		await workerMain();
 	}
 
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 25c6d7d0a4..b70b9a2f11 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -10,7 +10,7 @@ import {
 	updateMetaCache,
 	type Config,
 } from "backend-rs";
-import { config, envOption } from "@/config.js";
+import { config } from "@/config.js";
 import { db, initDb } from "@/db/postgre.js";
 import { inspect } from "node:util";
 
@@ -39,9 +39,7 @@ export async function masterMain() {
 
 	bootLogger.info("Firefish initialized");
 
-	if (!envOption.disableClustering) {
-		await spawnWorkers(config.clusterLimits);
-	}
+	await spawnWorkers(config.clusterLimits);
 
 	bootLogger.info(
 		`Now listening on port ${config.port} on ${config.url}`,
@@ -49,14 +47,12 @@ export async function masterMain() {
 		true,
 	);
 
-	if (!envOption.noDaemons) {
-		import("../daemons/server-stats.js").then((x) => x.default());
-		import("../daemons/queue-stats.js").then((x) => x.default());
-		// Update meta cache every 5 minitues
-		setInterval(() => updateMetaCache(), 1000 * 60 * 5);
-		// Remove old attestation challenges
-		setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30);
-	}
+	import("../daemons/server-stats.js").then((x) => x.default());
+	import("../daemons/queue-stats.js").then((x) => x.default());
+	// Update meta cache every 5 minitues
+	setInterval(() => updateMetaCache(), 1000 * 60 * 5);
+	// Remove old attestation challenges
+	setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30);
 }
 
 function showEnvironment(): void {
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index c91294b611..5adab590a2 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -1,4 +1,3 @@
-import { loadConfig, loadEnv } from "backend-rs";
+import { loadConfig } from "backend-rs";
 
 export const config = loadConfig();
-export const envOption = loadEnv();
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index a604a1c9fa..f82f8cda3b 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -1,7 +1,7 @@
 import type httpSignature from "@peertube/http-signature";
 import { v4 as uuid } from "uuid";
 
-import { config, envOption } from "@/config.js";
+import { config } from "@/config.js";
 import type { DriveFile } from "@/models/entities/drive-file.js";
 import type { IActivity } from "@/remote/activitypub/type.js";
 import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js";
@@ -518,8 +518,6 @@ export function webhookDeliver(
 }
 
 export default function () {
-	if (envOption.onlyServer) return;
-
 	deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
 	inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
 	endedPollNotificationQueue.process(endedPollNotification);
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 765378085d..ba67c5a486 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -10,10 +10,9 @@ import Router from "@koa/router";
 import cors from "@koa/cors";
 import mount from "koa-mount";
 import koaLogger from "koa-logger";
-import * as slow from "koa-slow";
 
 import { IsNull } from "typeorm";
-import { config, envOption } from "@/config.js";
+import { config } from "@/config.js";
 import Logger from "@/services/logger.js";
 import { Users } from "@/models/index.js";
 import { fetchMeta, stringToAcct } from "backend-rs";
@@ -54,15 +53,6 @@ if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
 			serverLogger.debug(str);
 		}),
 	);
-
-	// Delay
-	if (envOption.slow) {
-		app.use(
-			slow({
-				delay: 3000,
-			}),
-		);
-	}
 }
 
 // HSTS
diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts
index 5b8b6b3ecd..f7843eac18 100644
--- a/packages/backend/src/services/logger.ts
+++ b/packages/backend/src/services/logger.ts
@@ -2,7 +2,7 @@ import cluster from "node:cluster";
 import chalk from "chalk";
 import { default as convertColor } from "color-convert";
 import { format as dateFormat } from "date-fns";
-import { config, envOption } from "@/config.js";
+import { config } from "@/config.js";
 
 import * as SyslogPro from "syslog-pro";
 
@@ -133,7 +133,6 @@ export default class Logger {
 								: null;
 
 		let log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`;
-		if (envOption.withLogTime) log = `${chalk.gray(time)} ${log}`;
 
 		console.log(important ? chalk.bold(log) : log);
 
@@ -212,8 +211,7 @@ export default class Logger {
 		// Fixed if statement is ignored when logLevel includes debug
 		if (
 			config.logLevel?.includes("debug") ||
-			process.env.NODE_ENV !== "production" ||
-			envOption.verbose
+			process.env.NODE_ENV !== "production"
 		) {
 			this.log("debug", message, data, important);
 		}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 987cad941a..96ed709aff 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -207,9 +207,6 @@ importers:
       koa-send:
         specifier: 5.0.1
         version: 5.0.1
-      koa-slow:
-        specifier: 2.1.0
-        version: 2.1.0
       megalodon:
         specifier: workspace:*
         version: link:../megalodon
@@ -1149,13 +1146,11 @@ packages:
   '@biomejs/cli-darwin-arm64@1.8.0':
     resolution: {integrity: sha512-dBAYzfIJ1JmWigKlWourT3sJ3I60LZPjqNwwlsyFjiv5AV7vPeWlHVVIImV2BpINwNjZQhpXnwDfVnGS4vr7AA==}
     engines: {node: '>=14.21.3'}
-    cpu: [arm64]
     os: [darwin]
 
   '@biomejs/cli-darwin-x64@1.8.0':
     resolution: {integrity: sha512-ZTTSD0bP0nn9UpRDGQrQNTILcYSj+IkxTYr3CAV64DWBDtQBomlk2oVKWzDaA1LOhpAsTh0giLCbPJaVk2jfMQ==}
     engines: {node: '>=14.21.3'}
-    cpu: [x64]
     os: [darwin]
 
   '@biomejs/cli-linux-arm64-musl@1.8.0':
@@ -1167,7 +1162,6 @@ packages:
   '@biomejs/cli-linux-arm64@1.8.0':
     resolution: {integrity: sha512-cx725jTlJS6dskvJJwwCQaaMRBKE2Qss7ukzmx27Rn/DXRxz6tnnBix4FUGPf1uZfwrERkiJlbWM05JWzpvvXg==}
     engines: {node: '>=14.21.3'}
-    cpu: [arm64]
     os: [linux]
 
   '@biomejs/cli-linux-x64-musl@1.8.0':
@@ -1179,7 +1173,6 @@ packages:
   '@biomejs/cli-linux-x64@1.8.0':
     resolution: {integrity: sha512-cmgmhlD4QUxMhL1VdaNqnB81xBHb3R7huVNyYnPYzP+AykZ7XqJbPd1KcWAszNjUk2AHdx0aLKEBwCOWemxb2g==}
     engines: {node: '>=14.21.3'}
-    cpu: [x64]
     os: [linux]
 
   '@biomejs/cli-win32-arm64@1.8.0':
@@ -5620,10 +5613,6 @@ packages:
     resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
     engines: {node: '>= 8'}
 
-  koa-slow@2.1.0:
-    resolution: {integrity: sha512-ii6s1zuZ51p+SY7WIrwjRi1tmPrNpeHEaw5UYi4h1QzAPmIcNk16e9zwKd9+eNNzI9n+Q2LXHAvt1MCfs7j/8w==}
-    engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
-
   koa-static@5.0.0:
     resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
     engines: {node: '>= 7.6.0'}
@@ -5721,9 +5710,6 @@ packages:
   lodash.isequal@4.5.0:
     resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
 
-  lodash.isregexp@3.0.5:
-    resolution: {integrity: sha512-VlV0abdYZs5asSYW1JW5W1f6gxf2SGQt90rzQp7UNTQ8KwcB3CprZe5crN1LIlCA/fB5R9xecrZijGSELJL8Yg==}
-
   lodash.map@4.6.0:
     resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==}
 
@@ -6621,10 +6607,6 @@ packages:
     resolution: {integrity: sha512-P8aonTNAnXWJn2pBIqyeWw0I/D4YDOfEavCVvbDG+wx3dCujQX0ENZiW5OcHfbd8HKLfVhCf4F/3Xivf1yWDiA==}
     engines: {node: '>=14.19.0'}
 
-  q@1.4.1:
-    resolution: {integrity: sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==}
-    engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
-
   qrcode-generator@1.4.4:
     resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
 
@@ -13609,11 +13591,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  koa-slow@2.1.0:
-    dependencies:
-      lodash.isregexp: 3.0.5
-      q: 1.4.1
-
   koa-static@5.0.0:
     dependencies:
       debug: 3.2.7
@@ -13802,8 +13779,6 @@ snapshots:
 
   lodash.isequal@4.5.0: {}
 
-  lodash.isregexp@3.0.5: {}
-
   lodash.map@4.6.0: {}
 
   lodash.memoize@4.1.2: {}
@@ -14699,8 +14674,6 @@ snapshots:
       opentype.js: 0.4.11
       pngjs: 7.0.0
 
-  q@1.4.1: {}
-
   qrcode-generator@1.4.4: {}
 
   qrcode-vue3@1.6.8:

From 4390dfcbfb1ae6706bddb2dbdd6e6672141c2ae7 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 09/25] perf (backend): store antenna cache in memory

---
 packages/backend-rs/index.d.ts                |  1 +
 packages/backend-rs/index.js                  |  3 ++-
 .../backend-rs/src/service/antenna/cache.rs   | 27 +++++++++++++++++++
 .../backend-rs/src/service/antenna/mod.rs     |  2 ++
 .../src/service/antenna/process_new_note.rs   | 27 ++++++-------------
 .../backend-rs/src/service/antenna/update.rs  |  7 +++++
 .../server/api/endpoints/antennas/create.ts   |  5 ++--
 .../server/api/endpoints/antennas/delete.ts   |  2 ++
 .../server/api/endpoints/antennas/update.ts   |  2 ++
 9 files changed, 54 insertions(+), 22 deletions(-)
 create mode 100644 packages/backend-rs/src/service/antenna/cache.rs
 create mode 100644 packages/backend-rs/src/service/antenna/update.rs

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<string>): Promise<void>
+export function updateAntennaCache(): Promise<void>
 export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise<void>
 export function unwatchNote(watcherId: string, noteId: string): Promise<void>
 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<Option<Vec<antenna::Model>>> = 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<Vec<antenna::Model>, 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<Vec<antenna::Model>, 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<Vec<antenna::Model>, Error> {
-    const CACHE_KEY: &str = "antennas";
-
-    if let Some(antennas) = cache::get::<Vec<antenna::Model>>(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);
 });

From 3cf1e306b12169aa650405209feb16d9db3e70e9 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 10/25] fix (backend): update meta cache in admin/update-meta

---
 packages/backend/src/server/api/endpoints/admin/update-meta.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index e5234ea720..210b5d25f5 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -2,6 +2,7 @@ import { Meta } from "@/models/entities/meta.js";
 import { insertModerationLog } from "@/services/insert-moderation-log.js";
 import { db } from "@/db/postgre.js";
 import define from "@/server/api/define.js";
+import { updateMetaCache } from "backend-rs";
 
 export const meta = {
 	tags: ["admin"],
@@ -583,5 +584,5 @@ export default define(meta, paramDef, async (ps, me) => {
 		}
 	});
 
-	insertModerationLog(me, "updateMeta");
+	await Promise.all([insertModerationLog(me, "updateMeta"), updateMetaCache()]);
 });

From 7413866885b65d53fad6f7a3c0909e6e35864276 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:39 +0900
Subject: [PATCH 11/25] chore (backend-rs): change function names

---
 packages/backend-rs/src/database/mod.rs        |  5 +++--
 packages/backend-rs/src/database/postgresql.rs |  8 ++++----
 packages/backend-rs/src/database/redis.rs      | 10 +++++-----
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs
index 24710ca792..e41b89f737 100644
--- a/packages/backend-rs/src/database/mod.rs
+++ b/packages/backend-rs/src/database/mod.rs
@@ -1,8 +1,9 @@
 //! Interfaces for accessing PostgreSQL and Redis
 
-pub use postgresql::db_conn;
+pub use postgresql::get_conn as db_conn;
+
 pub use redis::key as redis_key;
-pub use redis::redis_conn;
+pub use redis::get_conn as redis_conn;
 pub use redis::RedisConnError;
 
 pub mod cache;
diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs
index e5742c1fcf..2e9a76126e 100644
--- a/packages/backend-rs/src/database/postgresql.rs
+++ b/packages/backend-rs/src/database/postgresql.rs
@@ -29,7 +29,7 @@ async fn init_conn() -> Result<&'static DbConn, DbErr> {
 }
 
 /// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
-pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
+pub async fn get_conn() -> Result<&'static DbConn, DbErr> {
     match DB_CONN.get() {
         Some(conn) => Ok(conn),
         None => init_conn().await,
@@ -38,11 +38,11 @@ pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
 
 #[cfg(test)]
 mod unit_test {
-    use super::db_conn;
+    use super::get_conn;
 
     #[tokio::test]
     async fn connect() {
-        assert!(db_conn().await.is_ok());
-        assert!(db_conn().await.is_ok());
+        assert!(get_conn().await.is_ok());
+        assert!(get_conn().await.is_ok());
     }
 }
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
index d88d61d514..fd44e28afe 100644
--- a/packages/backend-rs/src/database/redis.rs
+++ b/packages/backend-rs/src/database/redis.rs
@@ -88,7 +88,7 @@ pub enum RedisConnError {
 }
 
 /// Returns an async [redis] connection managed by a [bb8] connection pool.
-pub async fn redis_conn(
+pub async fn get_conn(
 ) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
     if !CONN_POOL.initialized() {
         let init_res = init_conn_pool().await;
@@ -114,19 +114,19 @@ pub fn key(key: impl ToString) -> String {
 
 #[cfg(test)]
 mod unit_test {
-    use super::redis_conn;
+    use super::get_conn;
     use pretty_assertions::assert_eq;
     use redis::AsyncCommands;
 
     #[tokio::test]
     async fn connect() {
-        assert!(redis_conn().await.is_ok());
-        assert!(redis_conn().await.is_ok());
+        assert!(get_conn().await.is_ok());
+        assert!(get_conn().await.is_ok());
     }
 
     #[tokio::test]
     async fn access() {
-        let mut redis = redis_conn().await.unwrap();
+        let mut redis = get_conn().await.unwrap();
 
         let key = "CARGO_UNIT_TEST_KEY";
         let value = "CARGO_UNIT_TEST_VALUE";

From 40e5bf45bd2d88785a4509294ec9aa77e2328b72 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 12/25] chore (backend-rs): use async HTTP requests

---
 packages/backend-rs/src/misc/get_image_size.rs | 6 +++---
 packages/backend-rs/src/misc/latest_version.rs | 8 +++++---
 packages/backend-rs/src/util/http_client.rs    | 6 +++---
 3 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index bfe5c4edd3..46d9415434 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -1,6 +1,6 @@
 use crate::{database::cache, util::http_client};
 use image::{io::Reader, ImageError, ImageFormat};
-use isahc::ReadResponseExt;
+use isahc::AsyncReadResponseExt;
 use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag};
 use std::io::Cursor;
 use tokio::sync::Mutex;
@@ -70,7 +70,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
 
     tracing::info!("retrieving image from {}", url);
 
-    let mut response = http_client::client()?.get(url)?;
+    let mut response = http_client::client()?.get_async(url).await?;
 
     if !response.status().is_success() {
         tracing::info!("status: {}", response.status());
@@ -78,7 +78,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
         return Err(Error::Http(format!("Failed to get image from {}", url)));
     }
 
-    let image_bytes = response.bytes()?;
+    let image_bytes = response.bytes().await?;
 
     let reader = Reader::new(Cursor::new(&image_bytes)).with_guessed_format()?;
 
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 1792adee06..82d8185d56 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -1,7 +1,7 @@
 //! Fetch latest Firefish version from the Firefish repository
 
 use crate::{database::cache, util::http_client};
-use isahc::ReadResponseExt;
+use isahc::AsyncReadResponseExt;
 use serde::Deserialize;
 
 #[derive(thiserror::Error, Debug)]
@@ -29,7 +29,9 @@ async fn get_latest_version() -> Result<String, Error> {
         version: String,
     }
 
-    let mut response = http_client::client()?.get(UPSTREAM_PACKAGE_JSON_URL)?;
+    let mut response = http_client::client()?
+        .get_async(UPSTREAM_PACKAGE_JSON_URL)
+        .await?;
 
     if !response.status().is_success() {
         tracing::info!("status: {}", response.status());
@@ -39,7 +41,7 @@ async fn get_latest_version() -> Result<String, Error> {
         ));
     }
 
-    let res_parsed: Response = serde_json::from_str(&response.text()?)?;
+    let res_parsed: Response = serde_json::from_str(&response.text().await?)?;
 
     Ok(res_parsed.version)
 }
diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs
index 3e861966d2..711405b6a0 100644
--- a/packages/backend-rs/src/util/http_client.rs
+++ b/packages/backend-rs/src/util/http_client.rs
@@ -20,13 +20,13 @@ static CLIENT: OnceCell<HttpClient> = OnceCell::new();
 /// # Example
 /// ```no_run
 /// # use backend_rs::util::http_client::client;
-/// use isahc::ReadResponseExt;
+/// use isahc::AsyncReadResponseExt;
 ///
 /// # fn f() -> Result<(), Box<dyn std::error::Error>> {
-/// let mut response = client()?.get("https://example.com/")?;
+/// let mut response = client()?.get_async("https://example.com/").await?;
 ///
 /// if response.status().is_success() {
-///     println!("{}", response.text()?);
+///     println!("{}", response.text().await?);
 /// }
 /// # Ok(())
 /// # }

From 308ecec34122c1b9030209a4428246ca05ec8f77 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 13/25] chore: format

---
 packages/backend-rs/src/database/mod.rs                      | 2 +-
 packages/backend-rs/src/database/redis.rs                    | 4 ++--
 packages/backend/src/server/api/endpoints/antennas/create.ts | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs
index e41b89f737..03315c1080 100644
--- a/packages/backend-rs/src/database/mod.rs
+++ b/packages/backend-rs/src/database/mod.rs
@@ -2,8 +2,8 @@
 
 pub use postgresql::get_conn as db_conn;
 
-pub use redis::key as redis_key;
 pub use redis::get_conn as redis_conn;
+pub use redis::key as redis_key;
 pub use redis::RedisConnError;
 
 pub mod cache;
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
index fd44e28afe..266e563c8d 100644
--- a/packages/backend-rs/src/database/redis.rs
+++ b/packages/backend-rs/src/database/redis.rs
@@ -88,8 +88,8 @@ pub enum RedisConnError {
 }
 
 /// Returns an async [redis] connection managed by a [bb8] connection pool.
-pub async fn get_conn(
-) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
+pub async fn get_conn() -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError>
+{
     if !CONN_POOL.initialized() {
         let init_res = init_conn_pool().await;
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index dc16f9290f..69dd19b0cb 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -171,7 +171,7 @@ 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();
 

From b6c16b4e29840cf35ed93b864dae77ef1fd1b35f Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 14/25] chore (backend-rs): fix doc link

---
 packages/backend-rs/src/misc/password.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index c91de82562..454514d230 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -6,7 +6,7 @@ use argon2::{
     Argon2,
 };
 
-/// Hashes the given password using [Argon2] algorithm.
+/// Hashes the given password using [argon2] algorithm.
 #[crate::export]
 pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
     let salt = SaltString::generate(&mut OsRng);

From 5cbb172dbf14bd29bc66ba608f7bdb7acd327912 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 15/25] chore (backend): accept linter suggestions

---
 packages/backend/src/boot/worker.ts                         | 2 +-
 packages/backend/src/server/api/endpoints/i/2fa/key-done.ts | 2 +-
 packages/backend/src/server/api/private/signin.ts           | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts
index 0acdcd97c6..3b641f84a0 100644
--- a/packages/backend/src/boot/worker.ts
+++ b/packages/backend/src/boot/worker.ts
@@ -25,6 +25,6 @@ export async function workerMain() {
 
 	if (cluster.isWorker) {
 		// Send a 'ready' message to parent process
-		process.send!("ready");
+		process.send?.("ready");
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 0951369dd8..01413d6f37 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -75,7 +75,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	const credentialIdLength = authData.readUInt16BE(53);
 	const credentialId = authData.slice(55, 55 + credentialIdLength);
 	const publicKeyData = authData.slice(55 + credentialIdLength);
-	const publicKey: Map<Number, any> = new Map(
+	const publicKey: Map<number, any> = new Map(
 		Object.entries(decode(publicKeyData)).map(([key, value]) => [
 			Number(key),
 			value,
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index c2620d8113..e124977cf6 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -27,9 +27,9 @@ export default async (ctx: Koa.Context) => {
 	ctx.set("Access-Control-Allow-Credentials", "true");
 
 	const body = ctx.request.body as any;
-	const username = body["username"];
-	const password = body["password"];
-	const token = body["token"];
+	const username = body.username;
+	const password = body.password;
+	const token = body.token;
 
 	function error(status: number, error: { id: string }) {
 		ctx.status = status;

From cb3c300c49b25e71c33e4ce2f92a33d53264bd3c Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 16/25] style (backend): don't italicize MFM function contents

---
 packages/backend/src/mfm/to-html.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts
index 83a99ae386..fe853ce1ef 100644
--- a/packages/backend/src/mfm/to-html.ts
+++ b/packages/backend/src/mfm/to-html.ts
@@ -61,7 +61,7 @@ export function toHtml(
 		},
 
 		fn(node) {
-			const el = doc.createElement("i");
+			const el = doc.createElement("span");
 			appendChildren(node.children, el);
 			return el;
 		},

From 8fd3f82d0bba22f065e37cb5cfbb54d9e69b80fd Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:40 +0900
Subject: [PATCH 17/25] chore (backend-rs): update comment

---
 .../backend-rs/src/misc/remove_old_attestation_challenges.rs    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
index 2dc45c02eb..a36c34c664 100644
--- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
+++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
@@ -1,4 +1,4 @@
-// TODO: We want to get rid of this
+// TODO: Migrate to Redis
 
 use crate::{database::db_conn, model::entity::attestation_challenge};
 use chrono::{Duration, Utc};

From 64088819bea196e313eb58951f74d2e23c74d738 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:41 +0900
Subject: [PATCH 18/25] chore (backend-rs): update build script

---
 packages/backend-rs/Cargo.toml | 4 ++--
 packages/backend-rs/build.rs   | 3 +--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml
index a9bcb3777f..8dd935c1a0 100644
--- a/packages/backend-rs/Cargo.toml
+++ b/packages/backend-rs/Cargo.toml
@@ -6,7 +6,7 @@ rust-version = "1.74"
 
 [features]
 default = []
-napi = ["dep:napi", "dep:napi-derive"]
+napi = ["dep:napi", "dep:napi-derive", "dep:napi-build"]
 
 [lib]
 crate-type = ["cdylib", "lib"]
@@ -53,4 +53,4 @@ pretty_assertions = { workspace = true, features = ["std"] }
 tokio-test = { workspace = true }
 
 [build-dependencies]
-napi-build = { workspace = true }
+napi-build = { workspace = true, optional = true }
diff --git a/packages/backend-rs/build.rs b/packages/backend-rs/build.rs
index 9e5e97713c..71dd87aa16 100644
--- a/packages/backend-rs/build.rs
+++ b/packages/backend-rs/build.rs
@@ -1,9 +1,8 @@
-extern crate napi_build;
-
 fn main() {
     // watch the version in the project root package.json
     println!("cargo:rerun-if-changed=../../package.json");
 
     // napi
+    #[cfg(feature = "napi")]
     napi_build::setup();
 }

From 7c32f682d45efc3b2c64804d11cb736b11e8d86e Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:10:41 +0900
Subject: [PATCH 19/25] chore: update auto-generated file

---
 packages/backend-rs/index.d.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 2297322ec9..64ff701fc8 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -452,7 +452,7 @@ export function fromMastodonId(mastodonId: string): string | null
  * ```
  */
 export function nyaify(text: string, lang?: string | undefined | null): string
-/** Hashes the given password using [Argon2] algorithm. */
+/** Hashes the given password using [argon2] algorithm. */
 export function hashPassword(password: string): string
 /** Checks whether the given password and hash match. */
 export function verifyPassword(password: string, hash: string): boolean

From 5ff6003c6eec29031a7e1baef028817254095f95 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 6 Jun 2024 17:17:51 +0900
Subject: [PATCH 20/25] fix (backend-rs): fix doctest

---
 packages/backend-rs/src/util/http_client.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs
index 711405b6a0..167f5c0198 100644
--- a/packages/backend-rs/src/util/http_client.rs
+++ b/packages/backend-rs/src/util/http_client.rs
@@ -22,7 +22,7 @@ static CLIENT: OnceCell<HttpClient> = OnceCell::new();
 /// # use backend_rs::util::http_client::client;
 /// use isahc::AsyncReadResponseExt;
 ///
-/// # fn f() -> Result<(), Box<dyn std::error::Error>> {
+/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
 /// let mut response = client()?.get_async("https://example.com/").await?;
 ///
 /// if response.status().is_success() {

From ff49b8074126cacab30039909c9bc8adb8b96475 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Thu, 6 Jun 2024 12:05:41 +0000
Subject: [PATCH 21/25] fix(deps): update bull-board to v5.20.1

---
 packages/backend/package.json |  6 ++---
 pnpm-lock.yaml                | 44 +++++++++++++++++++----------------
 2 files changed, 27 insertions(+), 23 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 1e3c72576f..1ae5453de3 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -22,9 +22,9 @@
 		"@swc/core-android-arm64": "1.3.11"
 	},
 	"dependencies": {
-		"@bull-board/api": "5.20.0",
-		"@bull-board/koa": "5.20.0",
-		"@bull-board/ui": "5.20.0",
+		"@bull-board/api": "5.20.1",
+		"@bull-board/koa": "5.20.1",
+		"@bull-board/ui": "5.20.1",
 		"@discordapp/twemoji": "15.0.3",
 		"@koa/cors": "5.0.0",
 		"@koa/multer": "3.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 96ed709aff..ff5e969bf0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -43,14 +43,14 @@ importers:
   packages/backend:
     dependencies:
       '@bull-board/api':
-        specifier: 5.20.0
-        version: 5.20.0(@bull-board/ui@5.20.0)
+        specifier: 5.20.1
+        version: 5.20.1(@bull-board/ui@5.20.1)
       '@bull-board/koa':
-        specifier: 5.20.0
-        version: 5.20.0(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)
+        specifier: 5.20.1
+        version: 5.20.1(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)
       '@bull-board/ui':
-        specifier: 5.20.0
-        version: 5.20.0
+        specifier: 5.20.1
+        version: 5.20.1
       '@discordapp/twemoji':
         specifier: 15.0.3
         version: 15.0.3
@@ -1146,11 +1146,13 @@ packages:
   '@biomejs/cli-darwin-arm64@1.8.0':
     resolution: {integrity: sha512-dBAYzfIJ1JmWigKlWourT3sJ3I60LZPjqNwwlsyFjiv5AV7vPeWlHVVIImV2BpINwNjZQhpXnwDfVnGS4vr7AA==}
     engines: {node: '>=14.21.3'}
+    cpu: [arm64]
     os: [darwin]
 
   '@biomejs/cli-darwin-x64@1.8.0':
     resolution: {integrity: sha512-ZTTSD0bP0nn9UpRDGQrQNTILcYSj+IkxTYr3CAV64DWBDtQBomlk2oVKWzDaA1LOhpAsTh0giLCbPJaVk2jfMQ==}
     engines: {node: '>=14.21.3'}
+    cpu: [x64]
     os: [darwin]
 
   '@biomejs/cli-linux-arm64-musl@1.8.0':
@@ -1162,6 +1164,7 @@ packages:
   '@biomejs/cli-linux-arm64@1.8.0':
     resolution: {integrity: sha512-cx725jTlJS6dskvJJwwCQaaMRBKE2Qss7ukzmx27Rn/DXRxz6tnnBix4FUGPf1uZfwrERkiJlbWM05JWzpvvXg==}
     engines: {node: '>=14.21.3'}
+    cpu: [arm64]
     os: [linux]
 
   '@biomejs/cli-linux-x64-musl@1.8.0':
@@ -1173,6 +1176,7 @@ packages:
   '@biomejs/cli-linux-x64@1.8.0':
     resolution: {integrity: sha512-cmgmhlD4QUxMhL1VdaNqnB81xBHb3R7huVNyYnPYzP+AykZ7XqJbPd1KcWAszNjUk2AHdx0aLKEBwCOWemxb2g==}
     engines: {node: '>=14.21.3'}
+    cpu: [x64]
     os: [linux]
 
   '@biomejs/cli-win32-arm64@1.8.0':
@@ -1187,16 +1191,16 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@bull-board/api@5.20.0':
-    resolution: {integrity: sha512-WUSuHdunODSWX8rkZmTnXpJsrUHqLS6+H3IGN0MNh6ylM/plZxGJJ3jW4nTXePWASeLNoDC0gjRcziDjlodPnQ==}
+  '@bull-board/api@5.20.1':
+    resolution: {integrity: sha512-45aDhnOzWRrtUUAKHxdClSnLIus5f8BK3ATzb2IwI/BRgOi1lWTe1YG266hVqDdWXsUDxKzf75DAANKfAoEsRA==}
     peerDependencies:
-      '@bull-board/ui': 5.20.0
+      '@bull-board/ui': 5.20.1
 
-  '@bull-board/koa@5.20.0':
-    resolution: {integrity: sha512-jto1WNIBZscCSJMkGm4G4qrb5KhblyJT/Q/59k5hHg9fdogzW9PfR+UQj7OOjLTMNX3phtPNvL0jG7/3HfmtIA==}
+  '@bull-board/koa@5.20.1':
+    resolution: {integrity: sha512-xUEEpMsdzZWHZQMnfk4a9kFhCz4PQkMBnb/t/C2xYwG15nCLapHWqMd5xO59OxnWOZ5XIiLsJwtCuf0l5G+L8A==}
 
-  '@bull-board/ui@5.20.0':
-    resolution: {integrity: sha512-EuijEHzQ9EAaA8WD4B+iXpTWTvCpTU2pskVUOs8CFkp0AYBqIhQDOi1W3f+e3FMqwS81l2/WkVHKkd6QRzbRzA==}
+  '@bull-board/ui@5.20.1':
+    resolution: {integrity: sha512-RzNinC4FKHNuxzkIRsCL+n9iO5RxmF5YM7byCuuv1/UeFjtCtsLHFi6TI9ZgJsXETA2Uxq9Mg7ppncojUjrINw==}
 
   '@cbor-extract/cbor-extract-darwin-arm64@2.2.0':
     resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==}
@@ -8306,15 +8310,15 @@ snapshots:
   '@biomejs/cli-win32-x64@1.8.0':
     optional: true
 
-  '@bull-board/api@5.20.0(@bull-board/ui@5.20.0)':
+  '@bull-board/api@5.20.1(@bull-board/ui@5.20.1)':
     dependencies:
-      '@bull-board/ui': 5.20.0
+      '@bull-board/ui': 5.20.1
       redis-info: 3.1.0
 
-  '@bull-board/koa@5.20.0(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)':
+  '@bull-board/koa@5.20.1(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)':
     dependencies:
-      '@bull-board/api': 5.20.0(@bull-board/ui@5.20.0)
-      '@bull-board/ui': 5.20.0
+      '@bull-board/api': 5.20.1(@bull-board/ui@5.20.1)
+      '@bull-board/ui': 5.20.1
       ejs: 3.1.10
       koa: 2.15.3
       koa-mount: 4.0.0
@@ -8377,9 +8381,9 @@ snapshots:
       - walrus
       - whiskers
 
-  '@bull-board/ui@5.20.0':
+  '@bull-board/ui@5.20.1':
     dependencies:
-      '@bull-board/api': 5.20.0(@bull-board/ui@5.20.0)
+      '@bull-board/api': 5.20.1(@bull-board/ui@5.20.1)
 
   '@cbor-extract/cbor-extract-darwin-arm64@2.2.0':
     optional: true

From e288a619821da7413a921f15a10145c7ff0e8f90 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Thu, 6 Jun 2024 20:05:03 +0000
Subject: [PATCH 22/25] fix(deps): update dependency got to v14.4.1

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 11 ++++++-----
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 1ae5453de3..b270265b2c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -57,7 +57,7 @@
 		"firefish-js": "workspace:*",
 		"fluent-ffmpeg": "2.1.3",
 		"form-data": "4.0.0",
-		"got": "14.4.0",
+		"got": "14.4.1",
 		"gunzip-maybe": "1.4.2",
 		"hpagent": "1.2.0",
 		"ioredis": "5.4.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff5e969bf0..9c3daf53e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -148,8 +148,8 @@ importers:
         specifier: 4.0.0
         version: 4.0.0
       got:
-        specifier: 14.4.0
-        version: 14.4.0
+        specifier: 14.4.1
+        version: 14.4.1
       gunzip-maybe:
         specifier: 1.4.2
         version: 1.4.2
@@ -4800,8 +4800,8 @@ packages:
     resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
     engines: {node: '>=10.19.0'}
 
-  got@14.4.0:
-    resolution: {integrity: sha512-baa2HMfREJ9UQSXOPwWe0DNK+FT8Okcxe9kmTJvaetv2q/MUxq0qFzEnfSbxo+wj45/QioGcH5ZhuT9VBIPJ5Q==}
+  got@14.4.1:
+    resolution: {integrity: sha512-IvDJbJBUeexX74xNQuMIVgCRRuNOm5wuK+OC3Dc2pnSoh1AOmgc7JVj7WC+cJ4u0aPcO9KZ2frTXcqK4W/5qTQ==}
     engines: {node: '>=20'}
 
   graceful-fs@4.2.11:
@@ -12518,7 +12518,7 @@ snapshots:
       p-cancelable: 2.1.1
       responselike: 2.0.1
 
-  got@14.4.0:
+  got@14.4.1:
     dependencies:
       '@sindresorhus/is': 6.3.1
       '@szmarczak/http-timer': 5.0.1
@@ -12531,6 +12531,7 @@ snapshots:
       lowercase-keys: 3.0.0
       p-cancelable: 4.0.1
       responselike: 3.0.0
+      type-fest: 4.19.0
 
   graceful-fs@4.2.11: {}
 

From 5d4f66d34c2eba645f9cd40688f3b4b2ffa87273 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Thu, 6 Jun 2024 20:05:36 +0000
Subject: [PATCH 23/25] chore(deps): update dependency execa to v9.2.0

---
 package.json   |  2 +-
 pnpm-lock.yaml | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/package.json b/package.json
index e0b9ce6b4c..33b13daa34 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
 		"@biomejs/cli-linux-arm64": "1.8.0",
 		"@biomejs/cli-linux-x64": "1.8.0",
 		"@types/node": "20.14.2",
-		"execa": "9.1.0",
+		"execa": "9.2.0",
 		"pnpm": "9.2.0",
 		"typescript": "5.4.5"
 	}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff5e969bf0..88d80b7a1b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -31,8 +31,8 @@ importers:
         specifier: 20.14.2
         version: 20.14.2
       execa:
-        specifier: 9.1.0
-        version: 9.1.0
+        specifier: 9.2.0
+        version: 9.2.0
       pnpm:
         specifier: 9.2.0
         version: 9.2.0
@@ -4450,9 +4450,9 @@ packages:
     resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
     engines: {node: '>=10'}
 
-  execa@9.1.0:
-    resolution: {integrity: sha512-lSgHc4Elo2m6bUDhc3Hl/VxvUDJdQWI40RZ4KMY9bKRc+hgMOT7II/JjbNDhI8VnMtrCb7U/fhpJIkLORZozWw==}
-    engines: {node: '>=18'}
+  execa@9.2.0:
+    resolution: {integrity: sha512-vpOyYg7UAVKLAWWtRS2gAdgkT7oJbCn0me3gmUmxZih4kd3MF/oo8kNTBTIbkO3yuuF5uB4ZCZfn8BOolITYhg==}
+    engines: {node: ^18.19.0 || >=20.5.0}
 
   executable@4.1.1:
     resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==}
@@ -12117,7 +12117,7 @@ snapshots:
       signal-exit: 3.0.7
       strip-final-newline: 2.0.0
 
-  execa@9.1.0:
+  execa@9.2.0:
     dependencies:
       '@sindresorhus/merge-streams': 4.0.0
       cross-spawn: 7.0.3

From 763cc1914e043d7e345b1d709b4f8beac22ad31b Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Thu, 6 Jun 2024 20:06:01 +0000
Subject: [PATCH 24/25] fix(deps): update dependency aws-sdk to v2.1636.0

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 1ae5453de3..6cbd303c07 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -36,7 +36,7 @@
 		"adm-zip": "0.5.14",
 		"ajv": "8.16.0",
 		"archiver": "7.0.1",
-		"aws-sdk": "2.1635.0",
+		"aws-sdk": "2.1636.0",
 		"axios": "1.7.2",
 		"backend-rs": "workspace:*",
 		"blurhash": "2.0.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ff5e969bf0..269f1fa6f1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -85,8 +85,8 @@ importers:
         specifier: 7.0.1
         version: 7.0.1
       aws-sdk:
-        specifier: 2.1635.0
-        version: 2.1635.0
+        specifier: 2.1636.0
+        version: 2.1636.0
       axios:
         specifier: 1.7.2
         version: 1.7.2
@@ -3035,8 +3035,8 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
-  aws-sdk@2.1635.0:
-    resolution: {integrity: sha512-zab4y8ftjgcctYao33c1fr8Yx1wMuRlEbZT7hwEXcK8Ta3+LAEXJs1wKjPpium+KUwl6zw7wxhaIdEibiz6InA==}
+  aws-sdk@2.1636.0:
+    resolution: {integrity: sha512-0w/jOCYnwewLYjH4UCh3GTBjR/NMdvEKNrd1pnM4FvfJSmjfzCinDvmf5Qc6xeIrqPfrdYOoQh7NJhYeJScCIQ==}
     engines: {node: '>= 10.0.0'}
 
   axios@0.24.0:
@@ -10437,7 +10437,7 @@ snapshots:
     dependencies:
       possible-typed-array-names: 1.0.0
 
-  aws-sdk@2.1635.0:
+  aws-sdk@2.1636.0:
     dependencies:
       buffer: 4.9.2
       events: 1.1.1

From a0ebad442d7c0fd38ebdcb32017aab96c1987701 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Fri, 7 Jun 2024 07:06:22 +0900
Subject: [PATCH 25/25] chore (backend): fix import

---
 packages/backend/src/misc/translate.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/misc/translate.ts b/packages/backend/src/misc/translate.ts
index 50fa7b2b3b..d1bcd5e72e 100644
--- a/packages/backend/src/misc/translate.ts
+++ b/packages/backend/src/misc/translate.ts
@@ -2,7 +2,7 @@ import fetch from "node-fetch";
 import { Converter } from "opencc-js";
 import { getAgentByUrl } from "@/misc/fetch.js";
 import { fetchMeta } from "backend-rs";
-import type { PostLanguage } from "@/misc/langmap";
+import type { PostLanguage } from "firefish-js";
 import * as deepl from "deepl-node";
 
 // DeepL translate and LibreTranslate don't provide