chore (backend-rs): documents, organize exports, typo fixes

This commit is contained in:
naskya 2024-05-30 07:03:51 +09:00
parent 0471df369d
commit 890ca846d0
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
40 changed files with 284 additions and 97 deletions

View file

@ -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 actors 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).

View file

@ -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

View file

@ -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",

View file

@ -1,3 +1,5 @@
//! Server configurations and environment variables
pub use server::CONFIG;
pub mod constant;

View file

@ -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};

View file

@ -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;

View file

@ -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,
}
}

View file

@ -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())

View file

@ -1,2 +1,4 @@
//! Services used to federate with other servers
pub mod acct;
pub mod nodeinfo;

View file

@ -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);

View file

@ -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())
}

View file

@ -1,3 +1,5 @@
//! NodeInfo handler
pub mod fetch;
pub mod generate;
mod schema;

View file

@ -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)]

View file

@ -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);

View file

@ -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();

View file

@ -1,3 +1,5 @@
//! Initializers
pub mod greet;
pub mod log;
pub mod system_info;

View file

@ -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()?;

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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"
);
}
}

View file

@ -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()

View file

@ -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([

View file

@ -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;

View file

@ -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?;

View file

@ -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> =

View file

@ -1,3 +1,5 @@
//! Miscellaneous utilities
pub mod check_server_block;
pub mod check_word_mute;
pub mod convert_host;

View file

@ -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();

View file

@ -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 {

View file

@ -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()

View file

@ -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()

View file

@ -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};

View file

@ -1 +1,3 @@
//! Database structure, auto-generated by [sea_orm]
pub mod entity;

View file

@ -1,3 +1,5 @@
//! Services provided for local users
pub mod antenna;
pub mod note;
pub mod push_notification;

View file

@ -1,3 +1,5 @@
//! Shared [isahc] HTTP client
use crate::config::CONFIG;
use isahc::{config::*, HttpClient};
use once_cell::sync::OnceCell;

View file

@ -1,3 +1,5 @@
//! ID generation utility based on [cuid2]
use crate::config::CONFIG;
use basen::BASE36;
use chrono::{DateTime, NaiveDateTime, Utc};

View file

@ -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;

View file

@ -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()

View file

@ -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);

View file

@ -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,
/// }
/// ```
///