Merge branch 'develop' into feat/scylladb

This commit is contained in:
Namekuji 2023-08-08 00:30:05 -04:00
commit 36d7493264
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
14 changed files with 254 additions and 25 deletions

View file

@ -1002,6 +1002,7 @@ _aboutFirefish:
pleaseDonateToHost: Silakan pertimbangkan juga berdonasi ke server rumah kamu, {host}, pleaseDonateToHost: Silakan pertimbangkan juga berdonasi ke server rumah kamu, {host},
untuk membantu dengan biaya operasi. untuk membantu dengan biaya operasi.
donateHost: Berdonasi ke {host} donateHost: Berdonasi ke {host}
misskeyContributors: Kontributor Misskey
_nsfw: _nsfw:
respect: "Sembunyikan media NSFW" respect: "Sembunyikan media NSFW"
ignore: "Jangan sembunyikan media NSFW" ignore: "Jangan sembunyikan media NSFW"
@ -2163,3 +2164,4 @@ delete2fa: Nonaktifkan 2FA
delete2faConfirm: Ini akan menghapus 2FA secara permanen pada akun ini. Lanjutkan? delete2faConfirm: Ini akan menghapus 2FA secara permanen pada akun ini. Lanjutkan?
deletePasskeysConfirm: Ini akan menghapus semua passkeys dan kunci keamanan pada akun deletePasskeysConfirm: Ini akan menghapus semua passkeys dan kunci keamanan pada akun
ini secara permanen. Lanjutkan? ini secara permanen. Lanjutkan?
addRe: Tambahkan "re:" pada awal komentar balasan postingan dengan peringatan konten

View file

@ -5,7 +5,7 @@ headlineFirefish: Uma plataforma de mídia social descentralizada e de código a
que é gratuita para sempre! 🚀 que é gratuita para sempre! 🚀
search: Pesquisar search: Pesquisar
gotIt: Entendi! gotIt: Entendi!
introFirefish: Bem vinde! Firefish é uma plataforma de mídia social descentralizada introFirefish: Bem vindo! Firefish é uma plataforma de mídia social descentralizada
e de código aberto que é gratuita para sempre! 🚀 e de código aberto que é gratuita para sempre! 🚀
searchPlaceholder: Pesquise no Firefish searchPlaceholder: Pesquise no Firefish
notifications: Notificações notifications: Notificações
@ -110,3 +110,137 @@ followRequests: Pedidos de seguimento
unfollow: Parar de seguir unfollow: Parar de seguir
followRequestPending: Pedido de seguimento pendente followRequestPending: Pedido de seguimento pendente
enterEmoji: Insira um emoji enterEmoji: Insira um emoji
itsOff: Desativado
itsOn: Ativado
removeReaction: Retirar sua reação
reactionSettingDescription2: Arraste para reordenar, clique para deletar, pressione
“+” para adicionar.
rememberNoteVisibility: Lembrar configurações de visibilidade de postagem
enterFileName: Preencha o nome do arquivo
block: Bloquear
unblock: Desbloquear
suspend: Suspender
blockConfirm: Você tem certeza de que quer bloquear esta conta?
unblockConfirm: Você tem certeza de que quer desbloquear esta conta?
suspendConfirm: Você tem certeza de que quer suspender esta conta?
selectList: Selecione uma lista
selectChannel: Selecione um canal
addEmoji: Adicionar Emoji
settingGuide: Configurações recomendadas
cacheRemoteFilesDescription: Quando esta configuração está desativada, arquivos remotos
serão carregados diretamente do servidor remoto. Desativar isso irá diminuir o uso
de armazenamento, mas aumentar o tráfego, já que as thumbnails não serão geradas.
flagAsBot: Marcar esta conta como robô
flagAsCat: Você é um gato? 😺
flagAsCatDescription: Você receberá orelhas de gato e falará como um!
flagSpeakAsCat: Falar como um gato
dashboard: Painel
showFeaturedNotesInTimeline: Mostrar postagens em destaque nas linhas do tempo
objectStorage: Armazenamento de objetos
useObjectStorage: Utilizar armazenamento de objetos
objectStorageBaseUrl: URL base
objectStorageBucket: Balde
objectStorageBucketDesc: Por favor, especifique o nome do balde usado no seu provedor.
objectStorageRegion: Região
objectStorageRegionDesc: Especifique uma região como "xx-east-1". Se o seu serviço
não distingue entre as regiões, deixe esta em branco ou insira 'us-east-1'.
objectStorageUseSSL: Utilizar SSL
objectStorageUseSSLDesc: Desligue isso se você não utilizará HTTPS para conexões de
API
objectStorageUseProxy: Conecte-se por Proxy
lastUsedDate: Última utilização em
state: Estado
objectStorageUseProxyDesc: Desligue isso se você não utilizará um Proxy para conexões
com API
serverLogs: Logs de Servidor
details: Detalhes
nothing: Não há nada para ver aqui
installedDate: Autorizado em
sort: Ordenar
ascendingOrder: Ascendente
descendingOrder: Descendente
output: Saída
expandOnNoteClick: Abrir postagem ao clicar
updateRemoteUser: Atualizar informações do usuário remoto
deleteAllFiles: Excluir todos os arquivos
deleteAllFilesConfirm: Você tem certeza de que deseja excluir todos os arquivos?
yourAccountSuspendedTitle: Esta conta está suspensa
yourAccountSuspendedDescription: Esta conta foi suspensa por quebrar os termos de
serviço do servidor ou similares. Entre em contato com o administrador se você quiser
saber um motivo mais detalhado. Por favor, não crie uma nova conta.
menu: Menu
divider: Divisor
addItem: Adicionar Item
inboxUrl: URL da caixa de entrada
left: Esquerda
center: Centro
wide: Largo
narrow: Estreito
isModerator: Moderador
monthAndDay: '{day}/{month}'
pinned: Fixar ao perfil
pinnedNote: Postagem fixada
you: Você
clickToShow: Clique para exibir
showEmojisInReactionNotifications: Mostrar emojis nas notificações de reação
reactionSetting: Reações a exibir no seletor de reações
customEmojis: Emojis personalizados
emojis: Emojis
emojiName: Nome do Emoji
emoji: Emoji
emojiUrl: URL do Emoji
editWidgetsExit: Pronto
userSilenced: Este usuário está sendo silenciado.
objectStoragePrefix: Prefixo
volume: Volume
objectStorageS3ForcePathStyle: Use URLs de endpoint baseadas em caminho
none: Nenhum
masterVolume: Volume mestre
showInPage: Mostrar na página
expandOnNoteClickDesc: Se desativado, você ainda pode abrir postagens no menu do botão
direito do mouse ou clicando no timestamp.
disablePagesScript: Desativar o AiScript nas Páginas
isPatron: Patrono do Firefish
invite: Convite
inMb: Em megabytes
iconUrl: URL do Ícone
basicInfo: Informações básicas
pinnedUsers: Usuários fixados
fontSize: Tamanho da fonte
noFollowRequests: Você não tem nenhuma solicitação de seguimento pendente
openImageInNewTab: Abrir imagens em nova guia
local: Local
remote: Remoto
total: Total
appearance: Aparência
accessibility: Acessibilidade
accountSettings: Configurações de Conta
numberOfDays: Número de dias
hideThisNote: Esconder esta postagem
objectStoragePrefixDesc: Os arquivos serão armazenados em diretórios com este prefixo.
objectStorageEndpointDesc: Deixe isso vazio se você estiver usando AWS S3, de outra
forma especifique o endpoint como '<host>' ou '<host>:<port>', dependendo do serviço
que você está usando.
deleteAll: Excluir tudo
showFixedPostForm: Exibir o formulário de postagem no topo da linha do tempo
newNoteRecived: Há novas postagens
sounds: Sons
chooseEmoji: Selecione um emoji
unableToProcess: A operação não pôde ser concluída
recentUsed: Recentemente usado
install: Instalar
uninstall: Desinstalar
installedApps: Aplicações Autorizadas
removeAllFollowing: Parar de seguir todos os usuários seguidos
removeAllFollowingDescription: Executar isso faz parar de seguir todas as contas de
{host}. Por favor, execute isso se o servidor, por exemplo, não existir mais.
userSuspended: Este usuário foi suspenso.
isAdmin: Administrador
receiveFollowRequest: Solicitação de seguir recebida
followRequestAccepted: Solicitação de seguir aceita
add: Adicionar
reaction: Reações
enableEmojiReactions: Ativar reações com emoji
attachCancel: Remover anexo
flagShowTimelineReplies: Mostrar respostas na linha do tempo
addAccount: Adicionar conta

View file

@ -232,6 +232,12 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "basen"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbe4bb73fd931c4d1aaf53b35d1286c8a948ad00ec92c8e3c856f15fd027f43"
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.2.2" version = "0.2.2"
@ -1500,6 +1506,7 @@ name = "native-utils"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"basen",
"cfg-if", "cfg-if",
"chrono", "chrono",
"cuid2", "cuid2",
@ -1511,7 +1518,6 @@ dependencies = [
"once_cell", "once_cell",
"parse-display", "parse-display",
"pretty_assertions", "pretty_assertions",
"radix_fmt",
"rand", "rand",
"schemars", "schemars",
"sea-orm", "sea-orm",
@ -1972,12 +1978,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "radix_fmt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"

View file

@ -31,11 +31,11 @@ serde_json = "1.0.96"
thiserror = "1.0.40" thiserror = "1.0.40"
tokio = { version = "1.28.1", features = ["full"] } tokio = { version = "1.28.1", features = ["full"] }
utoipa = "3.3.0" utoipa = "3.3.0"
radix_fmt = "1.0.0"
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.13.1", default-features = false, features = ["napi6", "tokio_rt"], optional = true } napi = { version = "2.13.1", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
napi-derive = { version = "2.12.0", optional = true } napi-derive = { version = "2.12.0", optional = true }
basen = "0.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"

View file

@ -10,11 +10,11 @@ path = "src/lib.rs"
[features] [features]
default = [] default = []
convert = ["dep:native-utils", "dep:indicatif", "dep:futures"] convert = ["dep:indicatif", "dep:futures"]
[dependencies] [dependencies]
serde_json = "1.0.96" serde_json = "1.0.96"
native-utils = { path = "../", optional = true } native-utils = { path = "../" }
indicatif = { version = "0.17.4", features = ["tokio"], optional = true } indicatif = { version = "0.17.4", features = ["tokio"], optional = true }
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
futures = { version = "0.3.28", optional = true } futures = { version = "0.3.28", optional = true }

View file

@ -3,6 +3,7 @@ pub use sea_orm_migration::prelude::*;
mod m20230531_180824_drop_reversi; mod m20230531_180824_drop_reversi;
mod m20230627_185451_index_note_url; mod m20230627_185451_index_note_url;
mod m20230709_000510_move_antenna_to_cache; mod m20230709_000510_move_antenna_to_cache;
mod m20230806_170616_fix_antenna_stream_ids;
pub struct Migrator; pub struct Migrator;
@ -13,6 +14,7 @@ impl MigratorTrait for Migrator {
Box::new(m20230531_180824_drop_reversi::Migration), Box::new(m20230531_180824_drop_reversi::Migration),
Box::new(m20230627_185451_index_note_url::Migration), Box::new(m20230627_185451_index_note_url::Migration),
Box::new(m20230709_000510_move_antenna_to_cache::Migration), Box::new(m20230709_000510_move_antenna_to_cache::Migration),
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
] ]
} }
} }

View file

@ -85,7 +85,7 @@ impl MigrationTrait for Migration {
) )
.ignore(); .ignore();
} }
pipe.query::<()>(&mut redis_conn).unwrap(); pipe.query::<()>(&mut redis_conn).unwrap_or(());
} }
let copied = total_num - remaining; let copied = total_num - remaining;

View file

@ -0,0 +1,59 @@
use std::env;
use native_utils::util::id;
use redis::Commands;
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
let cache_url = env::var("CACHE_URL").unwrap();
let prefix = env::var("CACHE_PREFIX").unwrap();
let client = redis::Client::open(cache_url).unwrap();
let mut redis_conn = client.get_connection().unwrap();
let keys: Vec<String> = redis_conn
.keys(format!("{}:antennaTimeline:*", prefix))
.unwrap();
let key_len = keys.len();
println!(
"Fixing corrupted stream IDs: {} timelines to be fixed",
key_len
);
for (i, key) in keys.iter().enumerate() {
let all_elems: Vec<Vec<Vec<String>>> = redis_conn.xrange_all(key).unwrap(); // Get all post IDs in stream
let stream_ids = all_elems
.iter()
.map(|v| format!("{}-*", id::get_timestamp(&v[1][1]))); // Get correct stream id with timestamp
redis_conn.del::<_, ()>(key).unwrap();
for (j, v) in stream_ids.enumerate() {
redis_conn
.xadd(key, v, &[("note", &all_elems[j][1][1])])
.unwrap_or(());
}
if i % 10 == 0 {
println!(
"Fixing streams [{:.2}%]",
(i as f64 / key_len as f64) * 100_f64
);
}
}
println!("Fixing streams [100.00%]");
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
Ok(())
}
}

View file

@ -1,9 +1,9 @@
//! ID generation utility based on [cuid2] //! ID generation utility based on [cuid2]
use basen::BASE36;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use chrono::Utc; use chrono::Utc;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use radix_fmt::radix_36;
use std::cmp; use std::cmp;
use crate::impl_into_napi_error; use crate::impl_into_napi_error;
@ -46,13 +46,21 @@ pub fn create_id(date_num: i64) -> Result<String, ErrorUninitialized> {
let time = cmp::max(date_num - TIME_2000, 0); let time = cmp::max(date_num - TIME_2000, 0);
Ok(format!( Ok(format!(
"{:0>8}{}", "{:0>8}{}",
radix_36(time).to_string(), BASE36.encode_var_len(&(time as u64)),
gen.create_id() gen.create_id()
)) ))
} }
} }
} }
pub fn get_timestamp(id: &str) -> i64 {
let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
match n {
None => -1,
Some(n) => n as i64 + TIME_2000,
}
}
cfg_if! { cfg_if! {
if #[cfg(feature = "napi")] { if #[cfg(feature = "napi")] {
use napi_derive::napi; use napi_derive::napi;
@ -68,17 +76,23 @@ cfg_if! {
pub fn native_create_id(date_num: i64) -> String { pub fn native_create_id(date_num: i64) -> String {
create_id(date_num).unwrap() create_id(date_num).unwrap()
} }
#[napi]
pub fn native_get_timestamp(id: String) -> i64 {
get_timestamp(&id)
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod unit_test { mod unit_test {
use crate::util::id; use crate::util::id;
use chrono::Utc;
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::{assert_eq, assert_ne};
use std::thread; use std::thread;
#[test] #[test]
fn can_generate_unique_ids() { fn can_create_and_decode() {
assert_eq!(id::create_id(0), Err(id::ErrorUninitialized)); assert_eq!(id::create_id(0), Err(id::ErrorUninitialized));
id::init_id(16, ""); id::init_id(16, "");
assert_eq!(id::create_id(0).unwrap().len(), 16); assert_eq!(id::create_id(0).unwrap().len(), 16);
@ -86,5 +100,10 @@ mod unit_test {
let id1 = thread::spawn(|| id::create_id(0).unwrap()); let id1 = thread::spawn(|| id::create_id(0).unwrap());
let id2 = thread::spawn(|| id::create_id(0).unwrap()); let id2 = thread::spawn(|| id::create_id(0).unwrap());
assert_ne!(id1.join().unwrap(), id2.join().unwrap()); assert_ne!(id1.join().unwrap(), id2.join().unwrap());
let now = Utc::now().timestamp_millis();
let test_id = id::create_id(now).unwrap();
let timestamp = id::get_timestamp(&test_id);
assert_eq!(now, timestamp);
} }
} }

View file

@ -2,6 +2,7 @@ import config from "@/config/index.js";
import { import {
nativeCreateId, nativeCreateId,
nativeInitIdGenerator, nativeInitIdGenerator,
nativeGetTimestamp,
} from "native-utils/built/index.js"; } from "native-utils/built/index.js";
const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24); const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24);
@ -20,8 +21,6 @@ export function genId(date?: Date): string {
return nativeCreateId((date ?? new Date()).getTime()); return nativeCreateId((date ?? new Date()).getTime());
} }
const TIME_2000 = 946_684_800_000;
export function getTimestamp(id: string): number { export function getTimestamp(id: string): number {
return parseInt(id.slice(0, 8), 36) + TIME_2000; return nativeGetTimestamp(id);
} }

View file

@ -31,7 +31,7 @@ export async function deleteAccount(
userId: user.id, userId: user.id,
...(cursor ? { id: MoreThan(cursor) } : {}), ...(cursor ? { id: MoreThan(cursor) } : {}),
}, },
take: 100, take: 10,
order: { order: {
id: 1, id: 1,
}, },

View file

@ -2,7 +2,7 @@ import define from "../../define.js";
import readNote from "@/services/note/read.js"; import readNote from "@/services/note/read.js";
import { Antennas, Notes } from "@/models/index.js"; import { Antennas, Notes } from "@/models/index.js";
import { redisClient } from "@/db/redis.js"; import { redisClient } from "@/db/redis.js";
import { genId } from "@/misc/gen-id.js"; import { genId, getTimestamp } from "@/misc/gen-id.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js"; import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
@ -61,10 +61,22 @@ export default define(meta, paramDef, async (ps, user) => {
} }
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
let end = "+";
if (ps.untilDate) {
end = ps.untilDate.toString();
} else if (ps.untilId) {
end = getTimestamp(ps.untilId).toString();
}
let start = "-";
if (ps.sinceDate) {
start = ps.sinceDate.toString();
} else if (ps.sinceId) {
start = getTimestamp(ps.sinceId).toString();
}
const noteIdsRes = await redisClient.xrevrange( const noteIdsRes = await redisClient.xrevrange(
`antennaTimeline:${antenna.id}`, `antennaTimeline:${antenna.id}`,
ps.untilDate ?? "+", end,
ps.sinceDate ?? "-", start,
"COUNT", "COUNT",
limit, limit,
); );

View file

@ -14,13 +14,15 @@ import { serverLogger } from "../index.js";
import { isMimeImage } from "@/misc/is-mime-image.js"; import { isMimeImage } from "@/misc/is-mime-image.js";
export async function proxyMedia(ctx: Koa.Context) { export async function proxyMedia(ctx: Koa.Context) {
const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`; let url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`;
if (typeof url !== "string") { if (typeof url !== "string") {
ctx.status = 400; ctx.status = 400;
return; return;
} }
url = url.replace("//", "/");
const { hostname } = new URL(url); const { hostname } = new URL(url);
let resolvedIps; let resolvedIps;
try { try {

View file

@ -1,6 +1,6 @@
import type { Antenna } from "@/models/entities/antenna.js"; import type { Antenna } from "@/models/entities/antenna.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import { genId } from "@/misc/gen-id.js"; import { getTimestamp } from "@/misc/gen-id.js";
import { redisClient } from "@/db/redis.js"; import { redisClient } from "@/db/redis.js";
import { publishAntennaStream } from "@/services/stream.js"; import { publishAntennaStream } from "@/services/stream.js";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
@ -15,7 +15,7 @@ export async function addNoteToAntenna(
"MAXLEN", "MAXLEN",
"~", "~",
"200", "200",
"*", `${getTimestamp(note.id)}-*`,
"note", "note",
note.id, note.id,
); );