wip: drop emoji column
This commit is contained in:
parent
4bde3f126b
commit
bd1b5a9249
17 changed files with 590 additions and 51 deletions
1
packages/backend/native-utils/Cargo.lock
generated
1
packages/backend/native-utils/Cargo.lock
generated
|
@ -2446,6 +2446,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"indicatif",
|
||||||
"scylla",
|
"scylla",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||||
chrono = "0.4.26"
|
chrono = "0.4.26"
|
||||||
clap = { version = "4.3.11", features = ["derive"] }
|
clap = { version = "4.3.11", features = ["derive"] }
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
|
indicatif = { version = "0.17.6", features = ["tokio"] }
|
||||||
scylla = "0.8.2"
|
scylla = "0.8.2"
|
||||||
sea-orm = { version = "0.12.2", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
|
sea-orm = { version = "0.12.2", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
|
||||||
serde = { version = "1.0.171", features = ["derive"] }
|
serde = { version = "1.0.171", features = ["derive"] }
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TYPE emoji (
|
||||||
|
"name" text,
|
||||||
|
"url" text,
|
||||||
|
"width" int,
|
||||||
|
"height" int,
|
||||||
|
);
|
||||||
|
ALTER TABLE reaction ADD "emoji" frozen<emoji>;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE reaction DROP "emoji";
|
||||||
|
DROP TYPE emoji;
|
|
@ -0,0 +1,50 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "following")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
#[sea_orm(column_name = "createdAt")]
|
||||||
|
pub created_at: DateTimeWithTimeZone,
|
||||||
|
#[sea_orm(column_name = "followeeId")]
|
||||||
|
pub followee_id: String,
|
||||||
|
#[sea_orm(column_name = "followerId")]
|
||||||
|
pub follower_id: String,
|
||||||
|
#[sea_orm(column_name = "followerHost")]
|
||||||
|
pub follower_host: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followerInbox")]
|
||||||
|
pub follower_inbox: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followerSharedInbox")]
|
||||||
|
pub follower_shared_inbox: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followeeHost")]
|
||||||
|
pub followee_host: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followeeInbox")]
|
||||||
|
pub followee_inbox: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followeeSharedInbox")]
|
||||||
|
pub followee_shared_inbox: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::FolloweeId",
|
||||||
|
to = "super::user::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
User2,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::FollowerId",
|
||||||
|
to = "super::user::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
User1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -1,6 +1,10 @@
|
||||||
pub mod drive_file;
|
pub mod drive_file;
|
||||||
pub mod emoji;
|
pub mod emoji;
|
||||||
|
pub mod following;
|
||||||
pub mod note;
|
pub mod note;
|
||||||
pub mod poll_vote;
|
pub mod note_edit;
|
||||||
|
pub mod note_reaction;
|
||||||
pub mod poll;
|
pub mod poll;
|
||||||
|
pub mod poll_vote;
|
||||||
|
pub mod user;
|
||||||
pub mod sea_orm_active_enums;
|
pub mod sea_orm_active_enums;
|
||||||
|
|
|
@ -64,6 +64,34 @@ pub struct Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::note_edit::Entity")]
|
||||||
|
NoteEdit,
|
||||||
|
#[sea_orm(has_many = "super::note_reaction::Entity")]
|
||||||
|
NoteReaction,
|
||||||
|
#[sea_orm(has_one = "super::poll::Entity")]
|
||||||
|
Poll,
|
||||||
|
#[sea_orm(has_many = "super::poll_vote::Entity")]
|
||||||
|
PollVote,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::note_edit::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::NoteEdit.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::poll::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Poll.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::poll_vote::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::PollVote.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "note_edit")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
#[sea_orm(column_name = "noteId")]
|
||||||
|
pub note_id: String,
|
||||||
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
|
pub text: Option<String>,
|
||||||
|
pub cw: Option<String>,
|
||||||
|
#[sea_orm(column_name = "fileIds")]
|
||||||
|
pub file_ids: Vec<String>,
|
||||||
|
#[sea_orm(column_name = "updatedAt")]
|
||||||
|
pub updated_at: DateTimeWithTimeZone,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::note::Entity",
|
||||||
|
from = "Column::NoteId",
|
||||||
|
to = "super::note::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Note,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::note::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Note.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "note_reaction")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
#[sea_orm(column_name = "createdAt")]
|
||||||
|
pub created_at: DateTimeWithTimeZone,
|
||||||
|
#[sea_orm(column_name = "userId")]
|
||||||
|
pub user_id: String,
|
||||||
|
#[sea_orm(column_name = "noteId")]
|
||||||
|
pub note_id: String,
|
||||||
|
pub reaction: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::note::Entity",
|
||||||
|
from = "Column::NoteId",
|
||||||
|
to = "super::note::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Note,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::note::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Note.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -0,0 +1,79 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "user")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
#[sea_orm(column_name = "createdAt")]
|
||||||
|
pub created_at: DateTimeWithTimeZone,
|
||||||
|
#[sea_orm(column_name = "updatedAt")]
|
||||||
|
pub updated_at: Option<DateTimeWithTimeZone>,
|
||||||
|
#[sea_orm(column_name = "lastFetchedAt")]
|
||||||
|
pub last_fetched_at: Option<DateTimeWithTimeZone>,
|
||||||
|
pub username: String,
|
||||||
|
#[sea_orm(column_name = "usernameLower")]
|
||||||
|
pub username_lower: String,
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[sea_orm(column_name = "followersCount")]
|
||||||
|
pub followers_count: i32,
|
||||||
|
#[sea_orm(column_name = "followingCount")]
|
||||||
|
pub following_count: i32,
|
||||||
|
#[sea_orm(column_name = "notesCount")]
|
||||||
|
pub notes_count: i32,
|
||||||
|
#[sea_orm(column_name = "avatarId", unique)]
|
||||||
|
pub avatar_id: Option<String>,
|
||||||
|
#[sea_orm(column_name = "bannerId", unique)]
|
||||||
|
pub banner_id: Option<String>,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
#[sea_orm(column_name = "isSuspended")]
|
||||||
|
pub is_suspended: bool,
|
||||||
|
#[sea_orm(column_name = "isSilenced")]
|
||||||
|
pub is_silenced: bool,
|
||||||
|
#[sea_orm(column_name = "isLocked")]
|
||||||
|
pub is_locked: bool,
|
||||||
|
#[sea_orm(column_name = "isBot")]
|
||||||
|
pub is_bot: bool,
|
||||||
|
#[sea_orm(column_name = "isCat")]
|
||||||
|
pub is_cat: bool,
|
||||||
|
#[sea_orm(column_name = "isAdmin")]
|
||||||
|
pub is_admin: bool,
|
||||||
|
#[sea_orm(column_name = "isModerator")]
|
||||||
|
pub is_moderator: bool,
|
||||||
|
pub emojis: Vec<String>,
|
||||||
|
pub host: Option<String>,
|
||||||
|
pub inbox: Option<String>,
|
||||||
|
#[sea_orm(column_name = "sharedInbox")]
|
||||||
|
pub shared_inbox: Option<String>,
|
||||||
|
pub featured: Option<String>,
|
||||||
|
pub uri: Option<String>,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub token: Option<String>,
|
||||||
|
#[sea_orm(column_name = "isExplorable")]
|
||||||
|
pub is_explorable: bool,
|
||||||
|
#[sea_orm(column_name = "followersUri")]
|
||||||
|
pub followers_uri: Option<String>,
|
||||||
|
#[sea_orm(column_name = "lastActiveDate")]
|
||||||
|
pub last_active_date: Option<DateTimeWithTimeZone>,
|
||||||
|
#[sea_orm(column_name = "hideOnlineStatus")]
|
||||||
|
pub hide_online_status: bool,
|
||||||
|
#[sea_orm(column_name = "isDeleted")]
|
||||||
|
pub is_deleted: bool,
|
||||||
|
#[sea_orm(column_name = "driveCapacityOverrideMb")]
|
||||||
|
pub drive_capacity_override_mb: Option<i32>,
|
||||||
|
#[sea_orm(column_name = "movedToUri")]
|
||||||
|
pub moved_to_uri: Option<String>,
|
||||||
|
#[sea_orm(column_name = "alsoKnownAs", column_type = "Text", nullable)]
|
||||||
|
pub also_known_as: Option<String>,
|
||||||
|
#[sea_orm(column_name = "speakAsCat")]
|
||||||
|
pub speak_as_cat: bool,
|
||||||
|
#[sea_orm(column_name = "isIndexable")]
|
||||||
|
pub is_indexable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -2,8 +2,8 @@ pub use clap::Parser;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod error;
|
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
pub(crate) mod migrator;
|
pub(crate) mod migrator;
|
||||||
pub(crate) mod setup;
|
pub(crate) mod setup;
|
||||||
|
|
|
@ -183,7 +183,7 @@ impl Migrator {
|
||||||
self.down(1).await?;
|
self.down(1).await?;
|
||||||
|
|
||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
},
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Write, sync::OnceLock};
|
||||||
|
|
||||||
use futures::TryStreamExt;
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
use scylla::{Session, SessionBuilder, IntoUserType, FromUserType, ValueList, FromRow};
|
use futures::{future, TryStreamExt};
|
||||||
use sea_orm::{query::*, entity::*, Database};
|
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
|
||||||
|
use scylla::{
|
||||||
|
prepared_statement::PreparedStatement, FromUserType, IntoUserType, Session, SessionBuilder,
|
||||||
|
ValueList,
|
||||||
|
};
|
||||||
|
use sea_orm::{entity::*, query::*, Database, DatabaseConnection};
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc, NaiveDate};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{DbConfig, ScyllaConfig},
|
config::{DbConfig, ScyllaConfig},
|
||||||
entity::note,
|
entity::{drive_file, emoji, following, note, note_edit, note_reaction, poll, poll_vote, user},
|
||||||
error::Error,
|
error::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,13 +50,7 @@ impl Initializer {
|
||||||
let pool = Database::connect(&self.postgres_url).await?;
|
let pool = Database::connect(&self.postgres_url).await?;
|
||||||
let db_backend = pool.get_database_backend();
|
let db_backend = pool.get_database_backend();
|
||||||
|
|
||||||
let num_notes: i64 = note::Entity::find().select_only().column_as(note::Column::Id.count(), "count").into_tuple().one(&pool).await?.unwrap_or_default();
|
self.copy(&pool).await?;
|
||||||
|
|
||||||
println!("{num_notes} posts are being copied.")
|
|
||||||
|
|
||||||
let mut notes = note::Entity::find().stream(&pool).await?;
|
|
||||||
while let Some(note) = notes.try_next().await? {
|
|
||||||
}
|
|
||||||
|
|
||||||
let fk_pairs = vec![
|
let fk_pairs = vec![
|
||||||
("channel_note_pining", "FK_10b19ef67d297ea9de325cd4502"),
|
("channel_note_pining", "FK_10b19ef67d297ea9de325cd4502"),
|
||||||
|
@ -92,13 +89,308 @@ impl Initializer {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn copy(&self, db: &DatabaseConnection) -> Result<(), Error> {
|
||||||
|
let note_prepared = self.scylla.prepare(INSERT_NOTE).await?;
|
||||||
|
let home_prepared = self.scylla.prepare(INSERT_HOME_TIMELINE).await?;
|
||||||
|
let reaction_prepared = self.scylla.prepare(INSERT_REACTION).await?;
|
||||||
|
|
||||||
|
let num_notes: i64 = note::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column_as(note::Column::Id.count(), "count")
|
||||||
|
.into_tuple()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let num_reactions: i64 = note_reaction::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column_as(note_reaction::Column::Id.count(), "count")
|
||||||
|
.into_tuple()
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
println!("Copying notes from PostgreSQL to ScyllaDB.");
|
||||||
|
|
||||||
|
const PB_TMPL: &str =
|
||||||
|
"{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})";
|
||||||
|
let pb_style = ProgressStyle::with_template(PB_TMPL)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("#>-");
|
||||||
|
|
||||||
|
let note_pb = ProgressBar::new(num_notes as u64).with_style(pb_style.to_owned());
|
||||||
|
let reaction_pb = ProgressBar::new(num_reactions as u64).with_style(pb_style.to_owned());
|
||||||
|
|
||||||
|
let mut notes = note::Entity::find()
|
||||||
|
.order_by_asc(note::Column::Id)
|
||||||
|
.stream(db)
|
||||||
|
.await?;
|
||||||
|
let mut note_tasks = Vec::new();
|
||||||
|
while let Some(note) = notes.try_next().await? {
|
||||||
|
note_tasks.push(self.copy_note(note, db, ¬e_prepared, &home_prepared, ¬e_pb));
|
||||||
|
}
|
||||||
|
let mut reactions = note_reaction::Entity::find()
|
||||||
|
.order_by_asc(note_reaction::Column::Id)
|
||||||
|
.stream(db)
|
||||||
|
.await?;
|
||||||
|
let mut reaction_tasks = Vec::new();
|
||||||
|
while let Some(reaction) = reactions.try_next().await? {
|
||||||
|
reaction_tasks.push(self.copy_reaction(reaction, &reaction_prepared, &reaction_pb));
|
||||||
|
}
|
||||||
|
|
||||||
|
future::try_join_all(note_tasks).await?;
|
||||||
|
future::try_join_all(reaction_tasks).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy_note(
|
||||||
|
&self,
|
||||||
|
note: note::Model,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
prepared_note: &PreparedStatement,
|
||||||
|
prepared_home: &PreparedStatement,
|
||||||
|
pb: &ProgressBar,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let reply = match ¬e.reply_id {
|
||||||
|
None => None,
|
||||||
|
Some(id) => note::Entity::find_by_id(id).one(db).await?,
|
||||||
|
};
|
||||||
|
let renote = match ¬e.renote_id {
|
||||||
|
None => None,
|
||||||
|
Some(id) => note::Entity::find_by_id(id).one(db).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let files = get_attached_files(Some(note.to_owned()), db).await?;
|
||||||
|
let reply_files = get_attached_files(reply.to_owned(), db).await?;
|
||||||
|
let renote_files = get_attached_files(renote.to_owned(), db).await?;
|
||||||
|
|
||||||
|
let note_poll = note.find_related(poll::Entity).one(db).await?;
|
||||||
|
let poll_type = match note_poll {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(PollType {
|
||||||
|
multiple: v.multiple,
|
||||||
|
expires_at: v.expires_at.map(Into::into),
|
||||||
|
choices: HashMap::from_iter(
|
||||||
|
v.choices
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, v)| ((i + 1) as i32, v.to_string())),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let reactions: HashMap<String, i32> = match note.reactions.as_object() {
|
||||||
|
None => HashMap::new(),
|
||||||
|
Some(obj) => HashMap::from_iter(
|
||||||
|
obj.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.as_i64().unwrap_or_default() as i32)),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let edits = note.find_related(note_edit::Entity).all(db).await?;
|
||||||
|
let edits = edits.iter().map(|v| async {
|
||||||
|
let v = v.to_owned();
|
||||||
|
Ok(NoteEditHistoryType {
|
||||||
|
content: v.text,
|
||||||
|
cw: v.cw,
|
||||||
|
files: get_files(v.file_ids, db).await?,
|
||||||
|
updated_at: v.updated_at.into(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let edits: Vec<Result<NoteEditHistoryType, Error>> = futures::future::join_all(edits).await;
|
||||||
|
let edits: Vec<NoteEditHistoryType> = edits
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_ref().ok())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let scylla_note = NoteTable {
|
||||||
|
created_at_date: note.created_at.date_naive(),
|
||||||
|
created_at: note.created_at.into(),
|
||||||
|
id: note.id.to_owned(),
|
||||||
|
visibility: note.visibility.to_value(),
|
||||||
|
content: note.text,
|
||||||
|
name: note.name,
|
||||||
|
cw: note.cw,
|
||||||
|
local_only: note.local_only,
|
||||||
|
renote_count: note.renote_count as i32,
|
||||||
|
replies_count: note.replies_count as i32,
|
||||||
|
uri: note.uri,
|
||||||
|
url: note.url,
|
||||||
|
score: note.score,
|
||||||
|
files,
|
||||||
|
visible_user_ids: note.visible_user_ids,
|
||||||
|
mentions: note.mentions,
|
||||||
|
mentioned_remote_users: note.mentioned_remote_users,
|
||||||
|
emojis: note.emojis,
|
||||||
|
tags: note.tags,
|
||||||
|
has_poll: poll_type.is_some(),
|
||||||
|
poll: poll_type,
|
||||||
|
thread_id: note.thread_id,
|
||||||
|
channel_id: note.channel_id,
|
||||||
|
user_id: note.user_id.to_owned(),
|
||||||
|
user_host: note.user_host,
|
||||||
|
reply_id: note.reply_id,
|
||||||
|
reply_user_id: note.reply_user_id,
|
||||||
|
reply_user_host: note.reply_user_host,
|
||||||
|
reply_content: reply.as_ref().map(|v| v.text.to_owned()).flatten(),
|
||||||
|
reply_cw: reply.as_ref().map(|v| v.cw.to_owned()).flatten(),
|
||||||
|
reply_files,
|
||||||
|
renote_id: note.renote_id,
|
||||||
|
renote_user_id: note.renote_user_id,
|
||||||
|
renote_user_host: note.renote_user_host,
|
||||||
|
renote_content: renote.as_ref().map(|v| v.text.to_owned()).flatten(),
|
||||||
|
renote_cw: renote.as_ref().map(|v| v.cw.to_owned()).flatten(),
|
||||||
|
renote_files,
|
||||||
|
reactions,
|
||||||
|
note_edit: edits,
|
||||||
|
updated_at: note.updated_at.map(Into::into),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.scylla
|
||||||
|
.execute(prepared_note, scylla_note.to_owned())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut home_tasks = Vec::new();
|
||||||
|
let mut local_followers = following::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(following::Column::FollowerId)
|
||||||
|
.filter(following::Column::FolloweeId.eq(note.user_id))
|
||||||
|
.filter(following::Column::FollowerHost.is_null())
|
||||||
|
.into_tuple::<String>()
|
||||||
|
.stream(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
while let Some(follower_id) = local_followers.try_next().await? {
|
||||||
|
let s_note = scylla_note.to_owned();
|
||||||
|
let home = HomeTimelineTable {
|
||||||
|
feed_user_id: follower_id,
|
||||||
|
created_at_date: s_note.created_at_date,
|
||||||
|
created_at: s_note.created_at,
|
||||||
|
id: s_note.id,
|
||||||
|
visibility: s_note.visibility,
|
||||||
|
content: s_note.content,
|
||||||
|
name: s_note.name,
|
||||||
|
cw: s_note.cw,
|
||||||
|
local_only: s_note.local_only,
|
||||||
|
renote_count: s_note.renote_count,
|
||||||
|
replies_count: s_note.replies_count,
|
||||||
|
uri: s_note.uri,
|
||||||
|
url: s_note.url,
|
||||||
|
score: s_note.score,
|
||||||
|
files: s_note.files,
|
||||||
|
visible_user_ids: s_note.visible_user_ids,
|
||||||
|
mentions: s_note.mentions,
|
||||||
|
mentioned_remote_users: s_note.mentioned_remote_users,
|
||||||
|
emojis: s_note.emojis,
|
||||||
|
tags: s_note.tags,
|
||||||
|
has_poll: s_note.has_poll,
|
||||||
|
poll: s_note.poll,
|
||||||
|
thread_id: s_note.thread_id,
|
||||||
|
channel_id: s_note.channel_id,
|
||||||
|
user_id: s_note.user_id,
|
||||||
|
user_host: s_note.user_host,
|
||||||
|
reply_id: s_note.reply_id,
|
||||||
|
reply_user_id: s_note.reply_user_id,
|
||||||
|
reply_user_host: s_note.reply_user_host,
|
||||||
|
reply_content: s_note.reply_content,
|
||||||
|
reply_cw: s_note.reply_cw,
|
||||||
|
reply_files: s_note.reply_files,
|
||||||
|
renote_id: s_note.renote_id,
|
||||||
|
renote_user_id: s_note.renote_user_id,
|
||||||
|
renote_user_host: s_note.renote_user_host,
|
||||||
|
renote_content: s_note.renote_content,
|
||||||
|
renote_cw: s_note.renote_cw,
|
||||||
|
renote_files: s_note.renote_files,
|
||||||
|
reactions: s_note.reactions,
|
||||||
|
note_edit: s_note.note_edit,
|
||||||
|
updated_at: s_note.updated_at,
|
||||||
|
};
|
||||||
|
home_tasks.push(self.scylla.execute(prepared_home, home));
|
||||||
|
}
|
||||||
|
future::try_join_all(home_tasks).await?;
|
||||||
|
|
||||||
|
pb.inc(1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy_reaction(
|
||||||
|
&self,
|
||||||
|
reaction: note_reaction::Model,
|
||||||
|
prepared: &PreparedStatement,
|
||||||
|
pb: &ProgressBar,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let scylla_reaction = ReactionTable {
|
||||||
|
id: reaction.id,
|
||||||
|
note_id: reaction.note_id,
|
||||||
|
user_id: reaction.user_id,
|
||||||
|
reaction: reaction.reaction,
|
||||||
|
created_at: reaction.created_at.into(),
|
||||||
|
};
|
||||||
|
self.scylla.execute(prepared, scylla_reaction).await?;
|
||||||
|
|
||||||
|
pb.inc(1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, IntoUserType, FromUserType)]
|
fn map_drive_file(file: drive_file::Model) -> DriveFileType {
|
||||||
|
DriveFileType {
|
||||||
|
id: file.id,
|
||||||
|
r#type: file.r#type,
|
||||||
|
created_at: file.created_at.into(),
|
||||||
|
name: file.name,
|
||||||
|
comment: file.comment,
|
||||||
|
blurhash: file.blurhash,
|
||||||
|
url: file.url,
|
||||||
|
thumbnail_url: file.thumbnail_url,
|
||||||
|
is_sensitive: file.is_sensitive,
|
||||||
|
is_link: file.is_link,
|
||||||
|
md5: file.md5,
|
||||||
|
size: file.size,
|
||||||
|
width: file
|
||||||
|
.properties
|
||||||
|
.get("width")
|
||||||
|
.filter(|v| v.is_number())
|
||||||
|
.map(|v| v.as_i64().unwrap() as i32),
|
||||||
|
height: file
|
||||||
|
.properties
|
||||||
|
.get("height")
|
||||||
|
.filter(|v| v.is_number())
|
||||||
|
.map(|v| v.as_i64().unwrap() as i32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_attached_files(
|
||||||
|
note: Option<note::Model>,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
) -> Result<Vec<DriveFileType>, Error> {
|
||||||
|
match note {
|
||||||
|
None => Ok(vec![]),
|
||||||
|
Some(v) => Ok(get_files(v.file_ids, db).await?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_files(
|
||||||
|
file_ids: Vec<String>,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
) -> Result<Vec<DriveFileType>, Error> {
|
||||||
|
if file_ids.is_empty() {
|
||||||
|
Ok(vec![])
|
||||||
|
} else {
|
||||||
|
let files = drive_file::Entity::find()
|
||||||
|
.filter(drive_file::Column::Id.is_in(file_ids))
|
||||||
|
.all(db)
|
||||||
|
.await?;
|
||||||
|
Ok(files.iter().map(|v| map_drive_file(v.to_owned())).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, IntoUserType, FromUserType)]
|
||||||
struct DriveFileType {
|
struct DriveFileType {
|
||||||
id: String,
|
id: String,
|
||||||
#[scylla_crate(rename = "type")]
|
r#type: String,
|
||||||
kind: String,
|
|
||||||
#[scylla_crate(rename = "createdAt")]
|
#[scylla_crate(rename = "createdAt")]
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -117,24 +409,16 @@ struct DriveFileType {
|
||||||
height: Option<i32>,
|
height: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, IntoUserType, FromUserType)]
|
#[derive(Debug, Clone, IntoUserType, FromUserType)]
|
||||||
struct NoteEditHistoryType {
|
struct NoteEditHistoryType {
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
cw: Option<String>,
|
cw: Option<String>,
|
||||||
files: Vec<DriveFileType>,
|
files: Vec<DriveFileType>,
|
||||||
#[scylla_crate(rename = "updatedAt")]
|
#[scylla_crate(rename = "updatedAt")]
|
||||||
updated_at: DateTime<Utc>
|
updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, IntoUserType, FromUserType)]
|
#[derive(Debug, Clone, IntoUserType, FromUserType)]
|
||||||
struct EmojiType {
|
|
||||||
name: String,
|
|
||||||
url: String,
|
|
||||||
width: Option<i32>,
|
|
||||||
height: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, IntoUserType, FromUserType)]
|
|
||||||
struct PollType {
|
struct PollType {
|
||||||
#[scylla_crate(rename = "expiresAt")]
|
#[scylla_crate(rename = "expiresAt")]
|
||||||
expires_at: Option<DateTime<Utc>>,
|
expires_at: Option<DateTime<Utc>>,
|
||||||
|
@ -142,7 +426,7 @@ struct PollType {
|
||||||
choices: HashMap<i32, String>,
|
choices: HashMap<i32, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ValueList)]
|
#[derive(Clone, ValueList)]
|
||||||
struct NoteTable {
|
struct NoteTable {
|
||||||
created_at_date: NaiveDate,
|
created_at_date: NaiveDate,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
|
@ -164,11 +448,11 @@ struct NoteTable {
|
||||||
emojis: Vec<String>,
|
emojis: Vec<String>,
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
has_poll: bool,
|
has_poll: bool,
|
||||||
poll: PollType,
|
poll: Option<PollType>,
|
||||||
thread_id: Option<String>,
|
thread_id: Option<String>,
|
||||||
channel_id: Option<String>,
|
channel_id: Option<String>,
|
||||||
user_id: String,
|
user_id: String,
|
||||||
user_host: String,
|
user_host: Option<String>,
|
||||||
reply_id: Option<String>,
|
reply_id: Option<String>,
|
||||||
reply_user_id: Option<String>,
|
reply_user_id: Option<String>,
|
||||||
reply_user_host: Option<String>,
|
reply_user_host: Option<String>,
|
||||||
|
@ -183,7 +467,7 @@ struct NoteTable {
|
||||||
renote_files: Vec<DriveFileType>,
|
renote_files: Vec<DriveFileType>,
|
||||||
reactions: HashMap<String, i32>,
|
reactions: HashMap<String, i32>,
|
||||||
note_edit: Vec<NoteEditHistoryType>,
|
note_edit: Vec<NoteEditHistoryType>,
|
||||||
updated_at: DateTime<Utc>,
|
updated_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const INSERT_NOTE: &str = r#"
|
const INSERT_NOTE: &str = r#"
|
||||||
|
@ -254,11 +538,11 @@ struct HomeTimelineTable {
|
||||||
emojis: Vec<String>,
|
emojis: Vec<String>,
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
has_poll: bool,
|
has_poll: bool,
|
||||||
poll: PollType,
|
poll: Option<PollType>,
|
||||||
thread_id: Option<String>,
|
thread_id: Option<String>,
|
||||||
channel_id: Option<String>,
|
channel_id: Option<String>,
|
||||||
user_id: String,
|
user_id: String,
|
||||||
user_host: String,
|
user_host: Option<String>,
|
||||||
reply_id: Option<String>,
|
reply_id: Option<String>,
|
||||||
reply_user_id: Option<String>,
|
reply_user_id: Option<String>,
|
||||||
reply_user_host: Option<String>,
|
reply_user_host: Option<String>,
|
||||||
|
@ -273,7 +557,7 @@ struct HomeTimelineTable {
|
||||||
renote_files: Vec<DriveFileType>,
|
renote_files: Vec<DriveFileType>,
|
||||||
reactions: HashMap<String, i32>,
|
reactions: HashMap<String, i32>,
|
||||||
note_edit: Vec<NoteEditHistoryType>,
|
note_edit: Vec<NoteEditHistoryType>,
|
||||||
updated_at: DateTime<Utc>,
|
updated_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const INSERT_HOME_TIMELINE: &str = r#"
|
const INSERT_HOME_TIMELINE: &str = r#"
|
||||||
|
@ -328,17 +612,16 @@ struct ReactionTable {
|
||||||
note_id: String,
|
note_id: String,
|
||||||
user_id: String,
|
user_id: String,
|
||||||
reaction: String,
|
reaction: String,
|
||||||
emoji: EmojiType,
|
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const INSERT_REACTION: &str = r#"INSERT INTO reaction ("id", "noteId", "userId", "reaction", "emoji", "createdAt") VALUES (?, ?, ?, ?, ?, ?)"#;
|
const INSERT_REACTION: &str = r#"INSERT INTO reaction ("id", "noteId", "userId", "reaction", "createdAt") VALUES (?, ?, ?, ?, ?)"#;
|
||||||
|
|
||||||
#[derive(ValueList)]
|
#[derive(ValueList)]
|
||||||
struct PollVoteTable {
|
struct PollVoteTable {
|
||||||
note_id: String,
|
note_id: String,
|
||||||
user_id: String,
|
user_id: String,
|
||||||
user_host: String,
|
user_host: Option<String>,
|
||||||
choice: Vec<i32>,
|
choice: Vec<i32>,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
@ -349,5 +632,16 @@ const INSERT_POLL_VOTE: &str = r#"INSERT INTO poll_vote ("noteId", "userId", "us
|
||||||
struct NotificationTable {
|
struct NotificationTable {
|
||||||
target_id: String,
|
target_id: String,
|
||||||
created_at_date: NaiveDate,
|
created_at_date: NaiveDate,
|
||||||
|
created_at: DateTime<Utc>,
|
||||||
|
id: String,
|
||||||
|
notifier_id: Option<String>,
|
||||||
|
notifier_host: Option<String>,
|
||||||
|
r#type: String,
|
||||||
|
entity_id: Option<String>,
|
||||||
|
reatcion: Option<String>,
|
||||||
|
choice: Option<i32>,
|
||||||
|
custom_body: Option<String>,
|
||||||
|
custom_icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INSERT_NOTIFICATION: &str = r#"INSERT INTO notification ("targetId", "createdAtDate", "createdAt", "id", "notifierId", "notifierHost", "type", "entityId", "reaction", "choice", "customBody", "customHeader", "customIcon") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#;
|
||||||
|
|
|
@ -152,8 +152,8 @@ export const scyllaQueries = {
|
||||||
},
|
},
|
||||||
reaction: {
|
reaction: {
|
||||||
insert: `INSERT INTO reaction
|
insert: `INSERT INTO reaction
|
||||||
("id", "noteId", "userId", "reaction", "emoji", "createdAt")
|
("id", "noteId", "userId", "reaction", "createdAt")
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
select: {
|
select: {
|
||||||
byNoteId: `SELECT * FROM reaction_by_id WHERE "noteId" = ?`,
|
byNoteId: `SELECT * FROM reaction_by_id WHERE "noteId" = ?`,
|
||||||
byUserId: `SELECT * FROM reaction_by_user_id WHERE "userId" = ?`,
|
byUserId: `SELECT * FROM reaction_by_user_id WHERE "userId" = ?`,
|
||||||
|
|
|
@ -294,9 +294,7 @@ export function parseHomeTimeline(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScyllaNoteReaction extends NoteReaction {
|
export type ScyllaNoteReaction = NoteReaction;
|
||||||
emoji: PopulatedEmoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FeedType =
|
export type FeedType =
|
||||||
| "home"
|
| "home"
|
||||||
|
@ -318,7 +316,6 @@ export function parseScyllaReaction(row: types.Row): ScyllaNoteReaction {
|
||||||
userId: row.get("userId"),
|
userId: row.get("userId"),
|
||||||
reaction: row.get("reaction"),
|
reaction: row.get("reaction"),
|
||||||
createdAt: row.get("createdAt"),
|
createdAt: row.get("createdAt"),
|
||||||
emoji: row.get("emoji"),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
} from "@/misc/reaction-lib.js";
|
} from "@/misc/reaction-lib.js";
|
||||||
import type { NoteReaction } from "@/models/entities/note-reaction.js";
|
import type { NoteReaction } from "@/models/entities/note-reaction.js";
|
||||||
import {
|
import {
|
||||||
|
type PopulatedEmoji,
|
||||||
aggregateNoteEmojis,
|
aggregateNoteEmojis,
|
||||||
populateEmojis,
|
populateEmojis,
|
||||||
prefetchEmojis,
|
prefetchEmojis,
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emoji data will be cached in toDbReaction.
|
// Emoji data will be cached in toDbReaction.
|
||||||
const { name: _reaction, emoji: emojiData } = await toDbReaction(
|
const { name: _reaction } = await toDbReaction(
|
||||||
reaction,
|
reaction,
|
||||||
user.host,
|
user.host,
|
||||||
);
|
);
|
||||||
|
@ -74,7 +74,6 @@ export default async (
|
||||||
record.noteId,
|
record.noteId,
|
||||||
record.userId,
|
record.userId,
|
||||||
_reaction,
|
_reaction,
|
||||||
emojiData,
|
|
||||||
record.createdAt,
|
record.createdAt,
|
||||||
],
|
],
|
||||||
{ prepare: true },
|
{ prepare: true },
|
||||||
|
|
Loading…
Reference in a new issue