diff --git a/packages/backend/native-utils/Cargo.lock b/packages/backend/native-utils/Cargo.lock index eb41326c..3d48b5ef 100644 --- a/packages/backend/native-utils/Cargo.lock +++ b/packages/backend/native-utils/Cargo.lock @@ -1431,6 +1431,7 @@ dependencies = [ "thiserror", "tokio", "url", + "urlencoding", ] [[package]] diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml index 161d1820..2f214f15 100644 --- a/packages/backend/native-utils/Cargo.toml +++ b/packages/backend/native-utils/Cargo.toml @@ -36,6 +36,7 @@ url = "2.5.0" napi = { version = "2.14.2", default-features = false, features = ["napi9", "tokio_rt", "chrono_date", "serde-json"] } napi-derive = { version = "2.14.6", optional = true } basen = "0.1.0" +urlencoding = "2.1.3" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/packages/backend/native-utils/src/database/error.rs b/packages/backend/native-utils/src/database/error.rs index f180870f..beb9f363 100644 --- a/packages/backend/native-utils/src/database/error.rs +++ b/packages/backend/native-utils/src/database/error.rs @@ -11,3 +11,12 @@ pub enum Error { } impl_napi_error_from!(Error); + +pub trait NapiDbErrExt { + fn into(self) -> napi::Error; +} +impl NapiDbErrExt for DbErr { + fn into(self) -> napi::Error { + napi::Error::from_reason(self.to_string()) + } +} diff --git a/packages/backend/native-utils/src/database/mod.rs b/packages/backend/native-utils/src/database/mod.rs index 739f39bb..73d9c400 100644 --- a/packages/backend/native-utils/src/database/mod.rs +++ b/packages/backend/native-utils/src/database/mod.rs @@ -1,26 +1,44 @@ pub mod error; +pub use error::NapiDbErrExt; -use error::Error; +use crate::config::server::read_server_config; use sea_orm::{Database, DbConn}; +use urlencoding::encode; -static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new(); - -pub async fn init_database(conn_uri: impl Into<String>) -> Result<(), Error> { - let conn = Database::connect(conn_uri.into()).await?; - DB_CONN.get_or_init(move || conn); - Ok(()) +#[napi_derive::napi] +pub struct JsDbConn { + inner: DbConn, } - -pub fn get_database() -> Result<&'static DbConn, Error> { - DB_CONN.get().ok_or(Error::Uninitialized) -} - -#[cfg(test)] -mod unit_test { - use super::{error::Error, get_database}; - - #[test] - fn error_uninitialized() { - assert_eq!(get_database().unwrap_err(), Error::Uninitialized); +impl JsDbConn { + pub fn inner(&self) -> &DbConn { + &self.inner } } + +#[napi_derive::napi] +pub async fn init_database() -> napi::Result<JsDbConn> { + let config = read_server_config().db; + let conn_uri = format!( + "postgres://{}:{}@{}:{}/{}", + config.user, + encode(&config.pass), + config.host, + config.port, + config.db, + ); + let conn = Database::connect(conn_uri) + .await + .map_err(NapiDbErrExt::into)?; + Ok(JsDbConn { inner: conn }) +} + +// TODO +// #[cfg(test)] +// mod unit_test { +// use super::{error::Error, get_database}; +// +// #[test] +// fn error_uninitialized() { +// assert_eq!(get_database().unwrap_err(), Error::Uninitialized); +// } +// } diff --git a/packages/backend/native-utils/src/model/entity/meta.rs b/packages/backend/native-utils/src/model/entity/meta.rs index 27a33fdd..2521d681 100644 --- a/packages/backend/native-utils/src/model/entity/meta.rs +++ b/packages/backend/native-utils/src/model/entity/meta.rs @@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "meta")] -#[napi_derive::napi(object)] +#[napi_derive::napi(object, js_name = "Meta")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, diff --git a/packages/backend/native-utils/src/util/fetch_meta.rs b/packages/backend/native-utils/src/util/fetch_meta.rs new file mode 100644 index 00000000..ad0578d5 --- /dev/null +++ b/packages/backend/native-utils/src/util/fetch_meta.rs @@ -0,0 +1,47 @@ +use sea_orm::{prelude::*, ActiveValue}; +use std::sync::Mutex; +// use sea_orm::{ActiveValue, prelude::*, sea_query::OnConflict}; +use crate::database::{JsDbConn, NapiDbErrExt}; +use crate::model::entity::meta; + +type Meta = meta::Model; + +static CACHE: Mutex<Option<Meta>> = Mutex::new(None); +fn update_cache(meta: &Meta) { + let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone())); +} + +#[napi_derive::napi] +pub async fn fetch_meta(conn: &JsDbConn, invalidate_cache: bool) -> napi::Result<Meta> { + // try using cache + if !invalidate_cache { + if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) { + return Ok(cache); + } + } + + // try fetching from db + let db = conn.inner(); + let meta = meta::Entity::find() + .one(db) + .await + .map_err(NapiDbErrExt::into)?; + if let Some(meta) = meta { + update_cache(&meta); + return Ok(meta); + } + + // create a new meta object and insert into db + let meta = meta::Entity::insert(meta::ActiveModel { + id: ActiveValue::Set("x".to_owned()), + ..Default::default() + }) + // FIXME handle on conflict + // .on_conflict(OnConflict::column(meta::Column::Id).do_nothing().to_owned()) + // .do_nothing() + .exec_with_returning(db) + .await + .map_err(NapiDbErrExt::into)?; + update_cache(&meta); + Ok(meta) +} diff --git a/packages/backend/native-utils/src/util/mod.rs b/packages/backend/native-utils/src/util/mod.rs index 79c10c95..fb695098 100644 --- a/packages/backend/native-utils/src/util/mod.rs +++ b/packages/backend/native-utils/src/util/mod.rs @@ -2,6 +2,7 @@ pub mod acct; pub mod convert_host; pub mod convert_to_hidden_post; pub mod escape_sql; +pub mod fetch_meta; pub mod format_milliseconds; pub mod id; pub mod random; diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts index b3a5e30a..2e781082 100644 --- a/packages/backend/src/misc/fetch-meta.ts +++ b/packages/backend/src/misc/fetch-meta.ts @@ -1,12 +1,10 @@ -import { db } from "@/db/postgre.js"; -import { Meta } from "@/models/entities/meta.js"; - -let cache: Meta; +import type { Meta } from "native-utils/built/index.js"; +import { fetchMeta } from "@/misc/native-utils.js"; export function metaToPugArgs(meta: Meta): object { let motd = ["Loading..."]; - if (meta.customMOTD.length > 0) { - motd = meta.customMOTD; + if (meta.customMotd.length > 0) { + motd = meta.customMotd; } let splashIconUrl = meta.iconUrl; if (meta.customSplashIcons.length > 0) { @@ -29,44 +27,7 @@ export function metaToPugArgs(meta: Meta): object { }; } -export async function fetchMeta(noCache = false): Promise<Meta> { - if (!noCache && cache) return cache; +export { fetchMeta } from "@/misc/native-utils.js"; - return await db.transaction(async (transactionalEntityManager) => { - // New IDs are prioritized because multiple records may have been created due to past bugs. - const metas = await transactionalEntityManager.find(Meta, { - order: { - id: "DESC", - }, - }); - - const meta = metas[0]; - - if (meta) { - cache = meta; - return meta; - } else { - // If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert. - const saved = await transactionalEntityManager - .upsert( - Meta, - { - id: "x", - }, - ["id"], - ) - .then((x) => - transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]), - ); - - cache = saved; - return saved; - } - }); -} - -setInterval(() => { - fetchMeta(true).then((meta) => { - cache = meta; - }); -}, 1000 * 10); +// refresh cache +setInterval(() => fetchMeta(true), 1000 * 10); diff --git a/packages/backend/src/misc/native-utils.ts b/packages/backend/src/misc/native-utils.ts index 1f73e6b9..6d960a52 100644 --- a/packages/backend/src/misc/native-utils.ts +++ b/packages/backend/src/misc/native-utils.ts @@ -1,15 +1,21 @@ import { readServerConfig, readEnvironmentConfig, + initDatabase, + fetchMeta as fetchMetaImpl, getFullApAccount as getFullApAccountImpl, isSelfHost as isSelfHostImpl, } from "native-utils/built/index.js"; export const serverConfig = readServerConfig(); export const envConfig = readEnvironmentConfig(); +const dbPromise = initDatabase(); type Option<T> = T | null | undefined; +export const fetchMeta = (invalidateCache = false) => + dbPromise.then((db) => fetchMetaImpl(db, invalidateCache)); + export function getFullApAccount( username: string, host: Option<string>, diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index 060b34ad..6f810cd2 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -4,7 +4,6 @@ import type { NoteReaction } from "@/models/entities/note-reaction.js"; import type { Note } from "@/models/entities/note.js"; import { Emojis } from "@/models/index.js"; import renderEmoji from "./emoji.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; export const renderLike = async (noteReaction: NoteReaction, note: Note) => { const reaction = noteReaction.reaction; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index d168c345..845c8f48 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -471,7 +471,7 @@ export default define(meta, paramDef, async (ps) => { uri: config.url, description: instance.description, langs: instance.langs, - tosUrl: instance.ToSUrl, + tosUrl: instance.toSUrl, moreUrls: instance.moreUrls, repositoryUrl: instance.repositoryUrl, feedbackUrl: instance.feedbackUrl, @@ -509,7 +509,7 @@ export default define(meta, paramDef, async (ps) => { defaultReaction: instance.defaultReaction, recommendedInstances: instance.recommendedInstances, pinnedUsers: instance.pinnedUsers, - customMOTD: instance.customMOTD, + customMOTD: instance.customMotd, customSplashIcons: instance.customSplashIcons, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, @@ -543,7 +543,7 @@ export default define(meta, paramDef, async (ps) => { objectStoragePort: instance.objectStoragePort, objectStorageAccessKey: instance.objectStorageAccessKey, objectStorageSecretKey: instance.objectStorageSecretKey, - objectStorageUseSSL: instance.objectStorageUseSSL, + objectStorageUseSSL: instance.objectStorageUseSsl, objectStorageUseProxy: instance.objectStorageUseProxy, objectStorageSetPublicRead: instance.objectStorageSetPublicRead, objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, diff --git a/packages/backend/src/server/api/endpoints/custom-motd.ts b/packages/backend/src/server/api/endpoints/custom-motd.ts index d61b31fe..2939355b 100644 --- a/packages/backend/src/server/api/endpoints/custom-motd.ts +++ b/packages/backend/src/server/api/endpoints/custom-motd.ts @@ -28,6 +28,6 @@ export const paramDef = { export default define(meta, paramDef, async () => { const meta = await fetchMeta(); - const motd = await Promise.all(meta.customMOTD.map((x) => x)); + const motd = await Promise.all(meta.customMotd.map((x) => x)); return motd; }); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 977d57a3..2c92f36e 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -425,7 +425,7 @@ export default define(meta, paramDef, async (ps, me) => { uri: config.url, description: instance.description, langs: instance.langs, - tosUrl: instance.ToSUrl, + tosUrl: instance.toSUrl, moreUrls: instance.moreUrls, repositoryUrl: instance.repositoryUrl, feedbackUrl: instance.feedbackUrl, diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index b7be1975..8c78accb 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -74,7 +74,7 @@ const nodeinfo2 = async () => { email: meta.maintainerEmail, }, langs: meta.langs, - tosUrl: meta.ToSUrl, + tosUrl: meta.toSUrl, repositoryUrl: meta.repositoryUrl, feedbackUrl: meta.feedbackUrl, disableRegistration: meta.disableRegistration,