refactor: move fetchMeta() to native-utils
This commit is contained in:
parent
d7d60ffdb0
commit
722b7ece8b
14 changed files with 116 additions and 73 deletions
1
packages/backend/native-utils/Cargo.lock
generated
1
packages/backend/native-utils/Cargo.lock
generated
|
@ -1431,6 +1431,7 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
|
|
47
packages/backend/native-utils/src/util/fetch_meta.rs
Normal file
47
packages/backend/native-utils/src/util/fetch_meta.rs
Normal file
|
@ -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)
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue