From 890ca846d093f5cd03674d401bee2baa359d2094 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Thu, 30 May 2024 07:03:51 +0900
Subject: [PATCH] chore (backend-rs): documents, organize exports, typo fixes

---
 docs/activitypub-extensions.md                |  2 +-
 packages/backend-rs/index.d.ts                | 96 +++++++++++++++++--
 packages/backend-rs/src/config/constant.rs    | 17 ++--
 packages/backend-rs/src/config/mod.rs         |  2 +
 packages/backend-rs/src/database/cache.rs     |  2 +
 packages/backend-rs/src/database/mod.rs       |  5 +-
 .../backend-rs/src/database/postgresql.rs     |  7 +-
 packages/backend-rs/src/database/redis.rs     | 11 ++-
 packages/backend-rs/src/federation/mod.rs     |  2 +
 .../src/federation/nodeinfo/fetch.rs          |  3 +
 .../src/federation/nodeinfo/generate.rs       |  4 +
 .../backend-rs/src/federation/nodeinfo/mod.rs |  2 +
 .../src/federation/nodeinfo/schema.rs         |  6 +-
 packages/backend-rs/src/init/greet.rs         |  1 +
 packages/backend-rs/src/init/log.rs           |  1 +
 packages/backend-rs/src/init/mod.rs           |  2 +
 packages/backend-rs/src/init/system_info.rs   | 13 ++-
 packages/backend-rs/src/lib.rs                |  8 --
 .../backend-rs/src/misc/check_server_block.rs | 65 ++++++++++---
 .../backend-rs/src/misc/check_word_mute.rs    | 14 +++
 packages/backend-rs/src/misc/convert_host.rs  | 42 +++-----
 packages/backend-rs/src/misc/emoji.rs         |  2 +
 packages/backend-rs/src/misc/escape_sql.rs    |  2 +
 .../src/misc/format_milliseconds.rs           |  2 +-
 .../backend-rs/src/misc/get_note_all_texts.rs |  4 +-
 .../backend-rs/src/misc/latest_version.rs     |  3 +
 packages/backend-rs/src/misc/mod.rs           |  2 +
 packages/backend-rs/src/misc/nyaify.rs        | 19 ++++
 packages/backend-rs/src/misc/password.rs      |  9 +-
 packages/backend-rs/src/misc/reaction.rs      |  3 +-
 .../misc/remove_old_attestation_challenges.rs |  2 +-
 packages/backend-rs/src/misc/system_info.rs   |  2 +
 packages/backend-rs/src/model/mod.rs          |  2 +
 packages/backend-rs/src/service/mod.rs        |  2 +
 packages/backend-rs/src/util/http_client.rs   |  2 +
 packages/backend-rs/src/util/id.rs            |  2 +
 packages/backend-rs/src/util/mod.rs           |  5 +-
 packages/backend-rs/src/util/random.rs        |  4 +-
 .../backend/src/server/api/private/signin.ts  |  1 +
 packages/macro-rs/src/lib.rs                  |  8 +-
 40 files changed, 284 insertions(+), 97 deletions(-)

diff --git a/docs/activitypub-extensions.md b/docs/activitypub-extensions.md
index 4d5faeff80..7c1e35ac91 100644
--- a/docs/activitypub-extensions.md
+++ b/docs/activitypub-extensions.md
@@ -5,6 +5,6 @@ These are the extensions to ActivityPub that Firefish implements. This page uses
 ## speakAsCat
 
 - Compact IRI: `firefish:speakAsCat`
-- Canonical IRI: `https://firefish.dev/ns#speakascat`
+- Canonical IRI: `https://firefish.dev/ns#speakAsCat`
 
 Used on actors to indicate that they not only identify as a cat, but also want to have their text be transformed to speak like one, expressed as a boolean value. If this property is set to true, displaying the actor’s posts will make them speak with “nya” instead of “na” and other cat-related text mannerisms. Used in combination with [misskey:isCat](https://misskey-hub.net/ns/#iscat).
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 30e59f5353..5060e38bdb 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -11,8 +11,9 @@ export const USER_ONLINE_THRESHOLD: number
 export const USER_ACTIVE_THRESHOLD: number
 /**
  * List of file types allowed to be viewed directly in the browser
+ *
  * Anything not included here will be responded as application/octet-stream
- * SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
+ * SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
  * * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
  * * <https://github.com/sindresorhus/file-type/blob/main/core.js>
  * * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
@@ -207,6 +208,7 @@ export interface Acct {
 }
 export function stringToAcct(acct: string): Acct
 export function acctToString(acct: Acct): string
+/** Fetches and returns the NodeInfo of a remote server. */
 export function fetchNodeinfo(host: string): Promise<Nodeinfo>
 export function nodeinfo_2_1(): Promise<any>
 export function nodeinfo_2_0(): Promise<any>
@@ -307,31 +309,86 @@ export interface Users {
   activeHalfyear: number | null
   activeMonth: number | null
 }
+/** Prints the greeting message and the Firefish version to stdout. */
 export function greet(): void
+/** Initializes the [tracing] logger. */
 export function initializeRustLogger(): void
+/** Prints the server hardware information as the server info log. */
 export function showServerInfo(): void
 /**
  * Checks if a server is blocked.
  *
- * ## Argument
+ * # Argument
  * `host` - punycoded instance host
+ *
+ * # Example
+ * ```no_run
+ * # use backend_rs::misc::check_server_block::is_blocked_server;
+ * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+ * assert_eq!(true, is_blocked_server("blocked.com").await?);
+ * assert_eq!(false, is_blocked_server("not-blocked.com").await?);
+ * assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?);
+ * assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?);
+ * # Ok(())
+ * # }
+ * ```
  */
 export function isBlockedServer(host: string): Promise<boolean>
 /**
  * Checks if a server is silenced.
  *
- * ## Argument
+ * # Argument
  * `host` - punycoded instance host
+ *
+ * # Example
+ * ```no_run
+ * # use backend_rs::misc::check_server_block::is_silenced_server;
+ * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+ * assert_eq!(true, is_silenced_server("silenced.com").await?);
+ * assert_eq!(false, is_silenced_server("not-silenced.com").await?);
+ * assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?);
+ * assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?);
+ * # Ok(())
+ * # }
+ * ```
  */
 export function isSilencedServer(host: string): Promise<boolean>
 /**
  * Checks if a server is allowlisted.
  * Returns `Ok(true)` if private mode is disabled.
  *
- * ## Argument
+ * # Argument
  * `host` - punycoded instance host
+ *
+ * # Example
+ * ```no_run
+ * # use backend_rs::misc::check_server_block::is_allowed_server;
+ * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+ * assert_eq!(true, is_allowed_server("allowed.com").await?);
+ * assert_eq!(false, is_allowed_server("not-allowed.com").await?);
+ * assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?);
+ * assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?);
+ * # Ok(())
+ * # }
+ * ```
  */
 export function isAllowedServer(host: string): Promise<boolean>
+/**
+ * Returns whether `note` should be hard-muted.
+ *
+ * More specifically, this function returns `Ok(true)`
+ * if and only if one or more of these conditions are met:
+ *
+ * * the note (text or CW) contains any of the words/patterns
+ * * the "parent" note(s) (reply, quote) contain any of the words/patterns
+ * * the alt text of the attached files contains any of the words/patterns
+ *
+ * # Arguments
+ *
+ * * `note` : [NoteLike] object
+ * * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
+ * * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
+ */
 export function checkWordMute(note: NoteLike, mutedWords: Array<string>, mutedPatterns: Array<string>): Promise<boolean>
 export function getFullApAccount(username: string, host?: string | undefined | null): string
 export function isSelfHost(host?: string | undefined | null): boolean
@@ -339,9 +396,11 @@ export function isSameOrigin(uri: string): boolean
 export function extractHost(uri: string): string
 export function toPuny(host: string): string
 export function isUnicodeEmoji(s: string): boolean
+/** Escapes `%` and `\` in the given string. */
 export function sqlLikeEscape(src: string): string
+/** Returns `true` if `src` does not contain suspicious characters like `%`. */
 export function safeForSql(src: string): boolean
-/** Convert milliseconds to a human readable string */
+/** Converts milliseconds to a human readable string. */
 export function formatMilliseconds(milliseconds: number): string
 export interface ImageSize {
   width: number
@@ -365,6 +424,7 @@ export interface NoteLikeForGetNoteSummary {
 export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
 export function isQuote(note: Note): boolean
 export function isSafeUrl(url: string): boolean
+/** Returns the latest Firefish version. */
 export function latestVersion(): Promise<string>
 export function toMastodonId(firefishId: string): string | null
 export function fromMastodonId(mastodonId: string): string | null
@@ -381,9 +441,31 @@ export interface PugArgs {
   privateMode: boolean | null
 }
 export function metaToPugArgs(meta: Meta): PugArgs
+/**
+ * Converts the given text into the cat language.
+ *
+ * refs:
+ * * <https://misskey-hub.net/ns/#isCat>
+ * * <https://firefish.dev/ns#speakAsCat>
+ *
+ * # Arguments
+ *
+ * * `text` : original text
+ * * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`)
+ *
+ * # Example
+ *
+ * ```
+ * # use backend_rs::misc::nyaify::nyaify;
+ * assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
+ * ```
+ */
 export function nyaify(text: string, lang?: string | undefined | null): string
+/** 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
+/** Returns whether the [bcrypt] algorithm is used for the password hash. */
 export function isOldPasswordAlgorithm(hash: string): boolean
 export interface DecodedReaction {
   reaction: string
@@ -393,7 +475,7 @@ export interface DecodedReaction {
 export function decodeReaction(reaction: string): DecodedReaction
 export function countReactions(reactions: Record<string, number>): Record<string, number>
 export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string>
-/** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */
+/** Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago */
 export function removeOldAttestationChallenges(): Promise<void>
 export interface Cpu {
   model: string
@@ -1336,6 +1418,6 @@ export function getTimestamp(id: string): number
 export function genId(): string
 /** Generate an ID using a specific datetime */
 export function genIdAt(date: Date): string
-/** Generate random string based on [thread_rng] and [Alphanumeric]. */
+/** Generates a random string based on [thread_rng] and [Alphanumeric]. */
 export function generateSecureRandomString(length: number): string
 export function generateUserToken(): string
diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs
index e0e7c7459a..db6e7bed00 100644
--- a/packages/backend-rs/src/config/constant.rs
+++ b/packages/backend-rs/src/config/constant.rs
@@ -1,24 +1,25 @@
-#[crate::export]
+#[crate::ts_export]
 pub const SECOND: i32 = 1000;
-#[crate::export]
+#[crate::ts_export]
 pub const MINUTE: i32 = 60 * SECOND;
-#[crate::export]
+#[crate::ts_export]
 pub const HOUR: i32 = 60 * MINUTE;
-#[crate::export]
+#[crate::ts_export]
 pub const DAY: i32 = 24 * HOUR;
 
-#[crate::export]
+#[crate::ts_export]
 pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
-#[crate::export]
+#[crate::ts_export]
 pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
 
 /// List of file types allowed to be viewed directly in the browser
+///
 /// Anything not included here will be responded as application/octet-stream
-/// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
+/// SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
 /// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
 /// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
 /// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
-#[crate::export]
+#[crate::ts_export]
 pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
     // Images
     "image/png",
diff --git a/packages/backend-rs/src/config/mod.rs b/packages/backend-rs/src/config/mod.rs
index 1bc5eaeb7e..0e8056a894 100644
--- a/packages/backend-rs/src/config/mod.rs
+++ b/packages/backend-rs/src/config/mod.rs
@@ -1,3 +1,5 @@
+//! Server configurations and environment variables
+
 pub use server::CONFIG;
 
 pub mod constant;
diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs
index 0488e0b0aa..bf675efa38 100644
--- a/packages/backend-rs/src/database/cache.rs
+++ b/packages/backend-rs/src/database/cache.rs
@@ -1,3 +1,5 @@
+//! Utilities for using Redis cache
+
 use crate::database::{redis_conn, redis_key, RedisConnError};
 use redis::{AsyncCommands, RedisError};
 use serde::{Deserialize, Serialize};
diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs
index e80c3c74a2..24710ca792 100644
--- a/packages/backend-rs/src/database/mod.rs
+++ b/packages/backend-rs/src/database/mod.rs
@@ -1,11 +1,10 @@
+//! Interfaces for accessing PostgreSQL and Redis
+
 pub use postgresql::db_conn;
 pub use redis::key as redis_key;
 pub use redis::redis_conn;
 pub use redis::RedisConnError;
 
-/// Utilities for using Redis cache
 pub mod cache;
-/// PostgreSQL interface
 pub mod postgresql;
-/// Redis interface
 pub mod redis;
diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs
index 13cfccb9c2..ed68227419 100644
--- a/packages/backend-rs/src/database/postgresql.rs
+++ b/packages/backend-rs/src/database/postgresql.rs
@@ -1,3 +1,5 @@
+//! PostgreSQL interface
+
 use crate::config::CONFIG;
 use once_cell::sync::OnceCell;
 use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
@@ -5,7 +7,7 @@ use tracing::log::LevelFilter;
 
 static DB_CONN: OnceCell<DbConn> = OnceCell::new();
 
-async fn init_database() -> Result<&'static DbConn, DbErr> {
+async fn init_conn() -> Result<&'static DbConn, DbErr> {
     let database_uri = format!(
         "postgres://{}:{}@{}:{}/{}",
         CONFIG.db.user,
@@ -24,10 +26,11 @@ async fn init_database() -> Result<&'static DbConn, DbErr> {
     Ok(DB_CONN.get_or_init(move || conn))
 }
 
+/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
 pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
     match DB_CONN.get() {
         Some(conn) => Ok(conn),
-        None => init_database().await,
+        None => init_conn().await,
     }
 }
 
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
index 2ef0499bb5..d88d61d514 100644
--- a/packages/backend-rs/src/database/redis.rs
+++ b/packages/backend-rs/src/database/redis.rs
@@ -1,18 +1,20 @@
+//! Redis interface
+
 use crate::config::CONFIG;
 use async_trait::async_trait;
 use bb8::{ManageConnection, Pool, PooledConnection, RunError};
 use redis::{aio::MultiplexedConnection, Client, ErrorKind, IntoConnectionInfo, RedisError};
 use tokio::sync::OnceCell;
 
-/// A `bb8::ManageConnection` for `redis::Client::get_multiplexed_async_connection`.
+/// A [bb8::ManageConnection] for [redis::Client::get_multiplexed_async_connection].
 #[derive(Clone, Debug)]
 pub struct RedisConnectionManager {
     client: Client,
 }
 
 impl RedisConnectionManager {
-    /// Create a new `RedisConnectionManager`.
-    /// See `redis::Client::open` for a description of the parameter types.
+    /// Creates a new [RedisConnectionManager].
+    /// See [redis::Client::open] for a description of the parameter types.
     pub fn new<T: IntoConnectionInfo>(info: T) -> Result<Self, RedisError> {
         Ok(Self {
             client: Client::open(info.into_connection_info()?)?,
@@ -85,6 +87,7 @@ pub enum RedisConnError {
     Bb8Pool(RunError<RedisError>),
 }
 
+/// Returns an async [redis] connection managed by a [bb8] connection pool.
 pub async fn redis_conn(
 ) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
     if !CONN_POOL.initialized() {
@@ -103,7 +106,7 @@ pub async fn redis_conn(
         .map_err(RedisConnError::Bb8Pool)
 }
 
-/// prefix redis key
+/// prefix Redis key
 #[inline]
 pub fn key(key: impl ToString) -> String {
     format!("{}:{}", CONFIG.redis_key_prefix, key.to_string())
diff --git a/packages/backend-rs/src/federation/mod.rs b/packages/backend-rs/src/federation/mod.rs
index 4251d784bf..722a91abdf 100644
--- a/packages/backend-rs/src/federation/mod.rs
+++ b/packages/backend-rs/src/federation/mod.rs
@@ -1,2 +1,4 @@
+//! Services used to federate with other servers
+
 pub mod acct;
 pub mod nodeinfo;
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index b68b222626..14ed838912 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -1,3 +1,5 @@
+//! NodeInfo fetcher
+
 use crate::federation::nodeinfo::schema::*;
 use crate::util::http_client;
 use isahc::AsyncReadResponseExt;
@@ -83,6 +85,7 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
 // for napi export
 type Nodeinfo = Nodeinfo20;
 
+/// Fetches and returns the NodeInfo of a remote server.
 #[crate::export]
 pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
     tracing::info!("fetching from {}", host);
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index 804dd3213c..b14c02643f 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -1,3 +1,5 @@
+//! NodeInfo generator
+
 use crate::config::CONFIG;
 use crate::database::{cache, db_conn};
 use crate::federation::nodeinfo::schema::*;
@@ -112,6 +114,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
     })
 }
 
+/// Returns NodeInfo (version 2.1) of the local server.
 pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
     const NODEINFO_2_1_CACHE_KEY: &str = "nodeinfo_2_1";
 
@@ -126,6 +129,7 @@ pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
     }
 }
 
+/// Returns NodeInfo (version 2.0) of the local server.
 pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, Error> {
     Ok(nodeinfo_2_1().await?.into())
 }
diff --git a/packages/backend-rs/src/federation/nodeinfo/mod.rs b/packages/backend-rs/src/federation/nodeinfo/mod.rs
index 4d1eb1fa90..d1e3fc2ed5 100644
--- a/packages/backend-rs/src/federation/nodeinfo/mod.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/mod.rs
@@ -1,3 +1,5 @@
+//! NodeInfo handler
+
 pub mod fetch;
 pub mod generate;
 mod schema;
diff --git a/packages/backend-rs/src/federation/nodeinfo/schema.rs b/packages/backend-rs/src/federation/nodeinfo/schema.rs
index b56c84ec70..33ab79e2f0 100644
--- a/packages/backend-rs/src/federation/nodeinfo/schema.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/schema.rs
@@ -1,9 +1,11 @@
+//! Schema definitions of NodeInfo version 2.0 and 2.1
+
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 
 // TODO: I want to use these macros but they don't work with rmp_serde
-// - #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86)
-// - #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
+// * #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86)
+// * #[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)]
diff --git a/packages/backend-rs/src/init/greet.rs b/packages/backend-rs/src/init/greet.rs
index 62854d3721..323548e59c 100644
--- a/packages/backend-rs/src/init/greet.rs
+++ b/packages/backend-rs/src/init/greet.rs
@@ -11,6 +11,7 @@ const GREETING_MESSAGE: &str = "\
  If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish
 ";
 
+/// Prints the greeting message and the Firefish version to stdout.
 #[crate::export]
 pub fn greet() {
     println!("{}", GREETING_MESSAGE);
diff --git a/packages/backend-rs/src/init/log.rs b/packages/backend-rs/src/init/log.rs
index 07966b77ad..beb49decdc 100644
--- a/packages/backend-rs/src/init/log.rs
+++ b/packages/backend-rs/src/init/log.rs
@@ -2,6 +2,7 @@ use crate::config::CONFIG;
 use tracing::Level;
 use tracing_subscriber::FmtSubscriber;
 
+/// Initializes the [tracing] logger.
 #[crate::export(js_name = "initializeRustLogger")]
 pub fn initialize_logger() {
     let mut builder = FmtSubscriber::builder();
diff --git a/packages/backend-rs/src/init/mod.rs b/packages/backend-rs/src/init/mod.rs
index e76c732184..b84bb27537 100644
--- a/packages/backend-rs/src/init/mod.rs
+++ b/packages/backend-rs/src/init/mod.rs
@@ -1,3 +1,5 @@
+//! Initializers
+
 pub mod greet;
 pub mod log;
 pub mod system_info;
diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs
index dc5b9b8aa9..138e7486e0 100644
--- a/packages/backend-rs/src/init/system_info.rs
+++ b/packages/backend-rs/src/init/system_info.rs
@@ -5,10 +5,21 @@ pub type SysinfoPoisonError = PoisonError<MutexGuard<'static, System>>;
 
 static SYSTEM_INFO: OnceLock<Mutex<System>> = OnceLock::new();
 
-pub fn system_info() -> &'static std::sync::Mutex<sysinfo::System> {
+/// Gives an access to the shared static [System] object.
+///
+/// # Example
+///
+/// ```
+/// # use backend_rs::init::system_info::{system_info, SysinfoPoisonError};
+/// let system_info = system_info().lock()?;
+/// println!("The number of CPU threads is {}.", system_info.cpus().len());
+/// # Ok::<(), SysinfoPoisonError>(())
+/// ```
+pub fn system_info() -> &'static std::sync::Mutex<System> {
     SYSTEM_INFO.get_or_init(|| Mutex::new(System::new_all()))
 }
 
+/// Prints the server hardware information as the server info log.
 #[crate::export]
 pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
     let system_info = system_info().lock()?;
diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs
index ed601b9f51..d62ab792d6 100644
--- a/packages/backend-rs/src/lib.rs
+++ b/packages/backend-rs/src/lib.rs
@@ -1,18 +1,10 @@
 use macro_rs::{export, ts_export};
 
-/// Server configurations and environment variables
 pub mod config;
-/// Interfaces for accessing PostgreSQL and Redis
 pub mod database;
-/// Services used to federate with other servers
 pub mod federation;
-/// Initializers
 pub mod init;
-/// Miscellaneous utilities
 pub mod misc;
-/// Database structure, auto-generated by [sea_orm]
 pub mod model;
-/// Services provided for local users
 pub mod service;
-/// Basic utilities such as ID generator and HTTP client
 pub mod util;
diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs
index bb7f40078a..fe0c118521 100644
--- a/packages/backend-rs/src/misc/check_server_block.rs
+++ b/packages/backend-rs/src/misc/check_server_block.rs
@@ -1,13 +1,26 @@
-use crate::misc::meta::fetch_meta;
-use sea_orm::DbErr;
+//! This module is used in the TypeScript backend only.
+// We may want to (re)implement these functions in the `federation` module
+// in a Rusty way (e.g., traits of server type) if needed.
 
 /// Checks if a server is blocked.
 ///
-/// ## Argument
+/// # Argument
 /// `host` - punycoded instance host
-#[crate::export]
-pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> {
-    Ok(fetch_meta(true)
+///
+/// # Example
+/// ```no_run
+/// # use backend_rs::misc::check_server_block::is_blocked_server;
+/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+/// assert_eq!(true, is_blocked_server("blocked.com").await?);
+/// assert_eq!(false, is_blocked_server("not-blocked.com").await?);
+/// assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?);
+/// assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?);
+/// # Ok(())
+/// # }
+/// ```
+#[crate::ts_export]
+pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
+    Ok(crate::misc::meta::fetch_meta(true)
         .await?
         .blocked_hosts
         .iter()
@@ -18,11 +31,23 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> {
 
 /// Checks if a server is silenced.
 ///
-/// ## Argument
+/// # Argument
 /// `host` - punycoded instance host
-#[crate::export]
-pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> {
-    Ok(fetch_meta(true)
+///
+/// # Example
+/// ```no_run
+/// # use backend_rs::misc::check_server_block::is_silenced_server;
+/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+/// assert_eq!(true, is_silenced_server("silenced.com").await?);
+/// assert_eq!(false, is_silenced_server("not-silenced.com").await?);
+/// assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?);
+/// assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?);
+/// # Ok(())
+/// # }
+/// ```
+#[crate::ts_export]
+pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
+    Ok(crate::misc::meta::fetch_meta(true)
         .await?
         .silenced_hosts
         .iter()
@@ -34,11 +59,23 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> {
 /// Checks if a server is allowlisted.
 /// Returns `Ok(true)` if private mode is disabled.
 ///
-/// ## Argument
+/// # Argument
 /// `host` - punycoded instance host
-#[crate::export]
-pub async fn is_allowed_server(host: &str) -> Result<bool, DbErr> {
-    let meta = fetch_meta(true).await?;
+///
+/// # Example
+/// ```no_run
+/// # use backend_rs::misc::check_server_block::is_allowed_server;
+/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
+/// assert_eq!(true, is_allowed_server("allowed.com").await?);
+/// assert_eq!(false, is_allowed_server("not-allowed.com").await?);
+/// assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?);
+/// assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?);
+/// # Ok(())
+/// # }
+/// ```
+#[crate::ts_export]
+pub async fn is_allowed_server(host: &str) -> Result<bool, sea_orm::DbErr> {
+    let meta = crate::misc::meta::fetch_meta(true).await?;
 
     if !meta.private_mode.unwrap_or(false) {
         return Ok(true);
diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs
index aba792baa5..4109cb9a36 100644
--- a/packages/backend-rs/src/misc/check_word_mute.rs
+++ b/packages/backend-rs/src/misc/check_word_mute.rs
@@ -26,6 +26,20 @@ fn check_word_mute_impl(
     })
 }
 
+/// Returns whether `note` should be hard-muted.
+///
+/// More specifically, this function returns `Ok(true)`
+/// if and only if one or more of these conditions are met:
+///
+/// * the note (text or CW) contains any of the words/patterns
+/// * the "parent" note(s) (reply, quote) contain any of the words/patterns
+/// * the alt text of the attached files contains any of the words/patterns
+///
+/// # Arguments
+///
+/// * `note` : [NoteLike] object
+/// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
+/// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
 #[crate::export]
 pub async fn check_word_mute(
     note: NoteLike,
diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs
index 2a88ce7ccd..c086285af1 100644
--- a/packages/backend-rs/src/misc/convert_host.rs
+++ b/packages/backend-rs/src/misc/convert_host.rs
@@ -1,4 +1,6 @@
-use crate::config::CONFIG;
+//! This module is used in the TypeScript backend only.
+// We may want to (re)implement these functions in the `federation` module
+// in a Rusty way (e.g., traits of actor type) if needed.
 
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
@@ -10,28 +12,28 @@ pub enum Error {
     NoHostname,
 }
 
-#[crate::export]
+#[crate::ts_export]
 pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String, Error> {
     Ok(match host {
         Some(host) => format!("{}@{}", username, to_puny(host)?),
-        None => format!("{}@{}", username, extract_host(&CONFIG.url)?),
+        None => format!("{}@{}", username, extract_host(&crate::config::CONFIG.url)?),
     })
 }
 
-#[crate::export]
+#[crate::ts_export]
 pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
     Ok(match host {
-        Some(host) => extract_host(&CONFIG.url)? == to_puny(host)?,
+        Some(host) => extract_host(&crate::config::CONFIG.url)? == to_puny(host)?,
         None => true,
     })
 }
 
-#[crate::export]
+#[crate::ts_export]
 pub fn is_same_origin(uri: &str) -> Result<bool, Error> {
-    Ok(url::Url::parse(uri)?.origin().ascii_serialization() == CONFIG.url)
+    Ok(url::Url::parse(uri)?.origin().ascii_serialization() == crate::config::CONFIG.url)
 }
 
-#[crate::export]
+#[crate::ts_export]
 pub fn extract_host(uri: &str) -> Result<String, Error> {
     url::Url::parse(uri)?
         .host_str()
@@ -39,29 +41,7 @@ pub fn extract_host(uri: &str) -> Result<String, Error> {
         .and_then(|v| Ok(to_puny(v)?))
 }
 
-#[crate::export]
+#[crate::ts_export]
 pub fn to_puny(host: &str) -> Result<String, idna::Errors> {
     idna::domain_to_ascii(host)
 }
-
-#[cfg(test)]
-mod unit_test {
-    use super::{extract_host, to_puny};
-    use pretty_assertions::assert_eq;
-
-    #[test]
-    fn extract_host_test() {
-        assert_eq!(
-            extract_host("https://firefish.dev/firefish/firefish.git").unwrap(),
-            "firefish.dev"
-        );
-    }
-
-    #[test]
-    fn to_puny_test() {
-        assert_eq!(
-            to_puny("何もかも.owari.shop").unwrap(),
-            "xn--u8jyfb5762a.owari.shop"
-        );
-    }
-}
diff --git a/packages/backend-rs/src/misc/emoji.rs b/packages/backend-rs/src/misc/emoji.rs
index b244dbd8e1..a19fc6229d 100644
--- a/packages/backend-rs/src/misc/emoji.rs
+++ b/packages/backend-rs/src/misc/emoji.rs
@@ -1,3 +1,5 @@
+//! This module is used in the TypeScript backend only.
+
 #[crate::ts_export]
 pub fn is_unicode_emoji(s: &str) -> bool {
     emojis::get(s).is_some()
diff --git a/packages/backend-rs/src/misc/escape_sql.rs b/packages/backend-rs/src/misc/escape_sql.rs
index c575e088ce..31b8b964ab 100644
--- a/packages/backend-rs/src/misc/escape_sql.rs
+++ b/packages/backend-rs/src/misc/escape_sql.rs
@@ -1,8 +1,10 @@
+/// Escapes `%` and `\` in the given string.
 #[crate::export]
 pub fn sql_like_escape(src: &str) -> String {
     src.replace('%', r"\%").replace('_', r"\_")
 }
 
+/// Returns `true` if `src` does not contain suspicious characters like `%`.
 #[crate::export]
 pub fn safe_for_sql(src: &str) -> bool {
     !src.contains([
diff --git a/packages/backend-rs/src/misc/format_milliseconds.rs b/packages/backend-rs/src/misc/format_milliseconds.rs
index dfa8df6f62..20c67a773a 100644
--- a/packages/backend-rs/src/misc/format_milliseconds.rs
+++ b/packages/backend-rs/src/misc/format_milliseconds.rs
@@ -1,4 +1,4 @@
-/// Convert milliseconds to a human readable string
+/// Converts milliseconds to a human readable string.
 #[crate::export]
 pub fn format_milliseconds(milliseconds: u32) -> String {
     let mut seconds = milliseconds / 1000;
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 6c926ea374..8199085a53 100644
--- a/packages/backend-rs/src/misc/get_note_all_texts.rs
+++ b/packages/backend-rs/src/misc/get_note_all_texts.rs
@@ -18,8 +18,8 @@ pub struct NoteLike {
 ///
 /// ## Arguments
 ///
-/// * `note` - [NoteLike] object
-/// * `include_parent` - whether to take the reply-to post and quoted post into account
+/// * `note` : [NoteLike] object
+/// * `include_parent` : whether to take the reply-to post and quoted post into account
 pub async fn all_texts(note: NoteLike, include_parent: bool) -> Result<Vec<String>, DbErr> {
     let db = db_conn().await?;
 
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 544f922e53..8c361aa5bf 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -1,3 +1,5 @@
+//! Fetch latest Firefish version from the Firefish repository
+
 use crate::database::cache;
 use crate::util::http_client;
 use isahc::ReadResponseExt;
@@ -43,6 +45,7 @@ async fn get_latest_version() -> Result<String, Error> {
     Ok(res_parsed.version)
 }
 
+/// Returns the latest Firefish version.
 #[crate::export]
 pub async fn latest_version() -> Result<String, Error> {
     let version: Option<String> =
diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs
index 3aefacc48c..23782b11a0 100644
--- a/packages/backend-rs/src/misc/mod.rs
+++ b/packages/backend-rs/src/misc/mod.rs
@@ -1,3 +1,5 @@
+//! Miscellaneous utilities
+
 pub mod check_server_block;
 pub mod check_word_mute;
 pub mod convert_host;
diff --git a/packages/backend-rs/src/misc/nyaify.rs b/packages/backend-rs/src/misc/nyaify.rs
index 94f1615d67..e9c5f414ca 100644
--- a/packages/backend-rs/src/misc/nyaify.rs
+++ b/packages/backend-rs/src/misc/nyaify.rs
@@ -1,6 +1,25 @@
+//! Cat language converter
+
 use once_cell::sync::Lazy;
 use regex::{Captures, Regex};
 
+/// Converts the given text into the cat language.
+///
+/// refs:
+/// * <https://misskey-hub.net/ns/#isCat>
+/// * <https://firefish.dev/ns#speakAsCat>
+///
+/// # Arguments
+///
+/// * `text` : original text
+/// * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`)
+///
+/// # Example
+///
+/// ```
+/// # use backend_rs::misc::nyaify::nyaify;
+/// assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
+/// ```
 #[crate::export]
 pub fn nyaify(text: &str, lang: Option<&str>) -> String {
     let mut to_return = text.to_owned();
diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index ecef7f6da8..c91de82562 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -1,9 +1,12 @@
+//! Utilities for password hash generation and verification
+
 use argon2::{
     password_hash,
     password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
     Argon2,
 };
 
+/// 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);
@@ -13,7 +16,7 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Er
 }
 
 #[derive(thiserror::Error, Debug)]
-pub enum VerifyError {
+pub enum Error {
     #[error("An error occured while bcrypt verification: {0}")]
     Bcrypt(#[from] bcrypt::BcryptError),
     #[error("Invalid argon2 password hash: {0}")]
@@ -22,8 +25,9 @@ pub enum VerifyError {
     Argon2(#[from] argon2::Error),
 }
 
+/// Checks whether the given password and hash match.
 #[crate::export]
-pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError> {
+pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
     if is_old_password_algorithm(hash) {
         Ok(bcrypt::verify(password, hash)?)
     } else {
@@ -34,6 +38,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError>
     }
 }
 
+/// Returns whether the [bcrypt] algorithm is used for the password hash.
 #[inline]
 #[crate::export]
 pub fn is_old_password_algorithm(hash: &str) -> bool {
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index d080649fb7..f07d4761cb 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -1,5 +1,4 @@
 use crate::database::db_conn;
-use crate::misc::convert_host::to_puny;
 use crate::misc::meta::fetch_meta;
 use crate::model::entity::emoji;
 use once_cell::sync::Lazy;
@@ -87,7 +86,7 @@ pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Resul
 
             if let Some(host) = host {
                 // remote emoji
-                let ascii_host = to_puny(host)?;
+                let ascii_host = idna::domain_to_ascii(host)?;
 
                 // TODO: Does SeaORM have the `exists` method?
                 if emoji::Entity::find()
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 75b8fcd7e3..58c0ea8f3a 100644
--- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
+++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
@@ -5,7 +5,7 @@ use crate::model::entity::attestation_challenge;
 use chrono::{Duration, Utc};
 use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter};
 
-/// Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago
+/// Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago
 #[crate::export]
 pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> {
     let res = attestation_challenge::Entity::delete_many()
diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs
index ec657a2f70..95bd0dc490 100644
--- a/packages/backend-rs/src/misc/system_info.rs
+++ b/packages/backend-rs/src/misc/system_info.rs
@@ -1,3 +1,5 @@
+//! Utilities to check hardware information such as cpu, memory, storage usage
+
 use crate::init::system_info::{system_info, SysinfoPoisonError};
 use sysinfo::{Disks, MemoryRefreshKind};
 
diff --git a/packages/backend-rs/src/model/mod.rs b/packages/backend-rs/src/model/mod.rs
index e8c3d6a4c6..0d8646a5f6 100644
--- a/packages/backend-rs/src/model/mod.rs
+++ b/packages/backend-rs/src/model/mod.rs
@@ -1 +1,3 @@
+//! Database structure, auto-generated by [sea_orm]
+
 pub mod entity;
diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs
index abbd7fd7c4..eedc5751b4 100644
--- a/packages/backend-rs/src/service/mod.rs
+++ b/packages/backend-rs/src/service/mod.rs
@@ -1,3 +1,5 @@
+//! Services provided for local users
+
 pub mod antenna;
 pub mod note;
 pub mod push_notification;
diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs
index c2c917964d..3e861966d2 100644
--- a/packages/backend-rs/src/util/http_client.rs
+++ b/packages/backend-rs/src/util/http_client.rs
@@ -1,3 +1,5 @@
+//! Shared [isahc] HTTP client
+
 use crate::config::CONFIG;
 use isahc::{config::*, HttpClient};
 use once_cell::sync::OnceCell;
diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs
index fb0c25b7d3..39e4c34045 100644
--- a/packages/backend-rs/src/util/id.rs
+++ b/packages/backend-rs/src/util/id.rs
@@ -1,3 +1,5 @@
+//! ID generation utility based on [cuid2]
+
 use crate::config::CONFIG;
 use basen::BASE36;
 use chrono::{DateTime, NaiveDateTime, Utc};
diff --git a/packages/backend-rs/src/util/mod.rs b/packages/backend-rs/src/util/mod.rs
index 21e9b157a4..761527c4ae 100644
--- a/packages/backend-rs/src/util/mod.rs
+++ b/packages/backend-rs/src/util/mod.rs
@@ -1,6 +1,5 @@
-/// Shared [isahc] HTTP client
+//! Basic utilities such as ID generator and HTTP client
+
 pub mod http_client;
-/// ID generation utility based on [cuid2]
 pub mod id;
-/// Secure random string generator
 pub mod random;
diff --git a/packages/backend-rs/src/util/random.rs b/packages/backend-rs/src/util/random.rs
index 4251a7c232..c757330f80 100644
--- a/packages/backend-rs/src/util/random.rs
+++ b/packages/backend-rs/src/util/random.rs
@@ -1,6 +1,8 @@
+//! Secure random string generator
+
 use rand::{distributions::Alphanumeric, thread_rng, Rng};
 
-/// Generate random string based on [thread_rng] and [Alphanumeric].
+/// Generates a random string based on [thread_rng] and [Alphanumeric].
 #[crate::export]
 pub fn generate_secure_random_string(length: u16) -> String {
     thread_rng()
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index d8bbbf74ad..1ed11bbbc2 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -94,6 +94,7 @@ export default async (ctx: Koa.Context) => {
 	// Compare passwords
 	const same = verifyPassword(password, profile.password!);
 
+	// Update the password hashing algorithm
 	if (same && isOldPasswordAlgorithm(profile.password!)) {
 		profile.password = hashPassword(password);
 		await UserProfiles.save(profile);
diff --git a/packages/macro-rs/src/lib.rs b/packages/macro-rs/src/lib.rs
index 3a1b972b9c..d723868f83 100644
--- a/packages/macro-rs/src/lib.rs
+++ b/packages/macro-rs/src/lib.rs
@@ -97,16 +97,16 @@ pub fn ts_export(
 /// ```
 /// #[macro_rs::napi(object)]
 /// struct Person {
-///   id: i32,
-///   name: String,
+///     id: i32,
+///     name: String,
 /// }
 /// ```
 /// simply becomes
 /// ```
 /// #[napi_derive::napi(use_nullable = true, object)]
 /// struct Person {
-///   id: i32,
-///   name: String,
+///     id: i32,
+///     name: String,
 /// }
 /// ```
 ///