Merge branch 'develop' into firefish-docs/develop
This commit is contained in:
commit
ff2221f951
|
@ -1,6 +1,8 @@
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
DELETE FROM "migrations" WHERE name IN (
|
DELETE FROM "migrations" WHERE name IN (
|
||||||
|
'FixMutingIndices1710690239308',
|
||||||
|
'NoteFile1710304584214',
|
||||||
'RenameMetaColumns1705944717480',
|
'RenameMetaColumns1705944717480',
|
||||||
'SeparateHardMuteWordsAndPatterns1706413792769',
|
'SeparateHardMuteWordsAndPatterns1706413792769',
|
||||||
'IndexAltTextAndCw1708872574733',
|
'IndexAltTextAndCw1708872574733',
|
||||||
|
@ -16,6 +18,20 @@ DELETE FROM "migrations" WHERE name IN (
|
||||||
'RemoveNativeUtilsMigration1705877093218'
|
'RemoveNativeUtilsMigration1705877093218'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- fix-muting-indices
|
||||||
|
DROP INDEX "IDX_renote_muting_createdAt";
|
||||||
|
DROP INDEX "IDX_renote_muting_muteeId";
|
||||||
|
DROP INDEX "IDX_renote_muting_muterId";
|
||||||
|
DROP INDEX "IDX_reply_muting_createdAt";
|
||||||
|
DROP INDEX "IDX_reply_muting_muteeId";
|
||||||
|
DROP INDEX "IDX_reply_muting_muterId";
|
||||||
|
CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt");
|
||||||
|
CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId");
|
||||||
|
CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId");
|
||||||
|
|
||||||
|
-- note-file
|
||||||
|
DROP TABLE "note_file";
|
||||||
|
|
||||||
-- rename-meta-columns
|
-- rename-meta-columns
|
||||||
ALTER TABLE "meta" RENAME COLUMN "tosUrl" TO "ToSUrl";
|
ALTER TABLE "meta" RENAME COLUMN "tosUrl" TO "ToSUrl";
|
||||||
ALTER TABLE "meta" RENAME COLUMN "objectStorageUseSsl" TO "objectStorageUseSSL";
|
ALTER TABLE "meta" RENAME COLUMN "objectStorageUseSsl" TO "objectStorageUseSSL";
|
||||||
|
|
|
@ -1198,7 +1198,9 @@ searchWordsDescription: "To search for posts, enter the search term. Separate wo
|
||||||
search.\nFor example, 'morning night' will find posts that contain both 'morning'
|
search.\nFor example, 'morning night' will find posts that contain both 'morning'
|
||||||
and 'night', and 'morning OR night' will find posts that contain either 'morning'
|
and 'night', and 'morning OR night' will find posts that contain either 'morning'
|
||||||
or 'night' (or both).\nYou can also combine AND/OR conditions like '(morning OR
|
or 'night' (or both).\nYou can also combine AND/OR conditions like '(morning OR
|
||||||
night) sleepy'.\n\nIf you want to go to a specific user page or post page, enter
|
night) sleepy'.\nIf you want to search for a sequence of words (e.g., a sentence), you
|
||||||
|
must put it in double quotes, not to make it an AND search: \"Today I learned\"\n\n
|
||||||
|
If you want to go to a specific user page or post page, enter
|
||||||
the ID or URL in this field and click the 'Lookup' button. Clicking 'Search' will
|
the ID or URL in this field and click the 'Lookup' button. Clicking 'Search' will
|
||||||
search for posts that literally contain the ID/URL."
|
search for posts that literally contain the ID/URL."
|
||||||
searchUsers: "Posted by (optional)"
|
searchUsers: "Posted by (optional)"
|
||||||
|
|
|
@ -1008,7 +1008,8 @@ enableTimelineStreaming: "タイムラインを自動で更新する"
|
||||||
searchWords: "検索語句・照会するIDやURL"
|
searchWords: "検索語句・照会するIDやURL"
|
||||||
searchWordsDescription: "投稿を検索するには、ここに検索語句を入力してください。空白区切りでAND検索になり、ORを挟むとOR検索になります。\n
|
searchWordsDescription: "投稿を検索するには、ここに検索語句を入力してください。空白区切りでAND検索になり、ORを挟むとOR検索になります。\n
|
||||||
例えば「朝 夜」と入力すると「朝」と「夜」が両方含まれた投稿を検索し、「朝 OR 夜」と入力すると「朝」または「夜」(または両方)が含まれた投稿を検索します。\n
|
例えば「朝 夜」と入力すると「朝」と「夜」が両方含まれた投稿を検索し、「朝 OR 夜」と入力すると「朝」または「夜」(または両方)が含まれた投稿を検索します。\n
|
||||||
「(朝 OR 夜) 眠い」のように、AND検索とOR検索を同時に行うこともできます。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID (@user@example.com)
|
「(朝 OR 夜) 眠い」のように、AND検索とOR検索を同時に行うこともできます。\n空白を含む文字列をAND検索ではなくそのまま検索したい場合、\"明日 買うもの\"\
|
||||||
|
\ のように二重引用符 (\") で囲む必要があります。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID (@user@example.com)
|
||||||
や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
|
や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
|
||||||
searchUsers: "投稿元(オプション)"
|
searchUsers: "投稿元(オプション)"
|
||||||
searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
|
searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
|
||||||
|
|
|
@ -1936,7 +1936,7 @@ moveFrom: 从旧账号迁移至此账号
|
||||||
defaultReaction: 发出和收到帖子的默认表情符号反应
|
defaultReaction: 发出和收到帖子的默认表情符号反应
|
||||||
sendModMail: 发送管理通知
|
sendModMail: 发送管理通知
|
||||||
moderationNote: "管理笔记"
|
moderationNote: "管理笔记"
|
||||||
ipFirstAcknowledged: "该日期是这个 IP 地址首次被获取到的日期"
|
ipFirstAcknowledged: "首次获取此 IP 地址的日期"
|
||||||
driveCapacityOverride: "网盘容量变更"
|
driveCapacityOverride: "网盘容量变更"
|
||||||
isLocked: 该账号设置了关注请求
|
isLocked: 该账号设置了关注请求
|
||||||
_filters:
|
_filters:
|
||||||
|
@ -2047,11 +2047,12 @@ publishTimelines: 为访客发布时间线
|
||||||
publishTimelinesDescription: 如果启用,在用户登出时本地和全局时间线也会显示在 {url} 上。
|
publishTimelinesDescription: 如果启用,在用户登出时本地和全局时间线也会显示在 {url} 上。
|
||||||
searchWordsDescription: "要搜索帖子,请输入关键词。交集搜索关键词之间使用空格进行区分,并集搜索关键词之间使用 OR 进行区分。\n例如 '早上
|
searchWordsDescription: "要搜索帖子,请输入关键词。交集搜索关键词之间使用空格进行区分,并集搜索关键词之间使用 OR 进行区分。\n例如 '早上
|
||||||
晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您还可以组合交集/并集条件,例如
|
晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您还可以组合交集/并集条件,例如
|
||||||
'(早上 OR 晚上) 困了' 。\n\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索”
|
'(早上 OR 晚上) 困了' 。\n如果您想搜索单词序列(例如一个英语句子),您必须将其放在双引号中,例如 \"Today I learned\" 以区分于交集搜索。\n
|
||||||
将搜索字面包含用户 ID/URL 的帖子。"
|
\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索” 将搜索字面包含用户 ID/URL
|
||||||
|
的帖子。"
|
||||||
searchRangeDescription: "如果您要过滤时间段,请按以下格式输入:20220615-20231031\n\n如果您省略年份(例如 0105-0106
|
searchRangeDescription: "如果您要过滤时间段,请按以下格式输入:20220615-20231031\n\n如果您省略年份(例如 0105-0106
|
||||||
或 20231105-0110),它将被解释为当前年份。\n\n您还可以省略开始日期或结束日期。 例如 -0102 将过滤搜索结果以仅显示今年 1 月 2 日之前发布的帖子,而
|
或 20231105-0110),它将被解释为当前年份。\n\n您还可以省略开始日期或结束日期。 例如 -0102 将过滤搜索结果以仅显示今年 1 月 2 日之前发布的帖子,而
|
||||||
20231026- 将过滤结果以仅显示 2023 年 10 月 26 日之后发布的帖子。"
|
20231026- 将过滤结果以仅显示 2023 年 10 月 26 日之后发布的帖子。"
|
||||||
messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。"
|
messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。"
|
||||||
noAltTextWarning: 有些附件没有说明。您是否忘记写说明了?
|
noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
|
||||||
showNoAltTextWarning: 当您尝试发布没有说明的帖子附件时显示警告
|
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
|
||||||
|
|
|
@ -64,6 +64,8 @@ pub enum Relation {
|
||||||
DriveFolder,
|
DriveFolder,
|
||||||
#[sea_orm(has_many = "super::messaging_message::Entity")]
|
#[sea_orm(has_many = "super::messaging_message::Entity")]
|
||||||
MessagingMessage,
|
MessagingMessage,
|
||||||
|
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||||
|
NoteFile,
|
||||||
#[sea_orm(has_many = "super::page::Entity")]
|
#[sea_orm(has_many = "super::page::Entity")]
|
||||||
Page,
|
Page,
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
|
@ -94,6 +96,12 @@ impl Related<super::messaging_message::Entity> for Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::note_file::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::NoteFile.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::page::Entity> for Entity {
|
impl Related<super::page::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::Page.def()
|
Relation::Page.def()
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub mod muting;
|
||||||
pub mod note;
|
pub mod note;
|
||||||
pub mod note_edit;
|
pub mod note_edit;
|
||||||
pub mod note_favorite;
|
pub mod note_favorite;
|
||||||
|
pub mod note_file;
|
||||||
pub mod note_reaction;
|
pub mod note_reaction;
|
||||||
pub mod note_thread_muting;
|
pub mod note_thread_muting;
|
||||||
pub mod note_unread;
|
pub mod note_unread;
|
||||||
|
|
|
@ -38,8 +38,6 @@ pub struct Model {
|
||||||
#[sea_orm(column_name = "visibleUserIds")]
|
#[sea_orm(column_name = "visibleUserIds")]
|
||||||
pub visible_user_ids: Vec<String>,
|
pub visible_user_ids: Vec<String>,
|
||||||
pub mentions: Vec<String>,
|
pub mentions: Vec<String>,
|
||||||
#[sea_orm(column_name = "mentionedRemoteUsers", column_type = "Text")]
|
|
||||||
pub mentioned_remote_users: String,
|
|
||||||
pub emojis: Vec<String>,
|
pub emojis: Vec<String>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
#[sea_orm(column_name = "hasPoll")]
|
#[sea_orm(column_name = "hasPoll")]
|
||||||
|
@ -100,6 +98,8 @@ pub enum Relation {
|
||||||
NoteEdit,
|
NoteEdit,
|
||||||
#[sea_orm(has_many = "super::note_favorite::Entity")]
|
#[sea_orm(has_many = "super::note_favorite::Entity")]
|
||||||
NoteFavorite,
|
NoteFavorite,
|
||||||
|
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||||
|
NoteFile,
|
||||||
#[sea_orm(has_many = "super::note_reaction::Entity")]
|
#[sea_orm(has_many = "super::note_reaction::Entity")]
|
||||||
NoteReaction,
|
NoteReaction,
|
||||||
#[sea_orm(has_many = "super::note_unread::Entity")]
|
#[sea_orm(has_many = "super::note_unread::Entity")]
|
||||||
|
@ -164,6 +164,12 @@ impl Related<super::note_favorite::Entity> for Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::note_file::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::NoteFile.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::note_reaction::Entity> for Entity {
|
impl Related<super::note_reaction::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::NoteReaction.def()
|
Relation::NoteReaction.def()
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.12
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "note_file")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(column_name = "serialNo", primary_key)]
|
||||||
|
pub serial_no: i64,
|
||||||
|
#[sea_orm(column_name = "noteId")]
|
||||||
|
pub note_id: String,
|
||||||
|
#[sea_orm(column_name = "fileId")]
|
||||||
|
pub file_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::drive_file::Entity",
|
||||||
|
from = "Column::FileId",
|
||||||
|
to = "super::drive_file::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
DriveFile,
|
||||||
|
#[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::drive_file::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::DriveFile.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::note::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Note.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -33,6 +33,7 @@ pub use super::muting::Entity as Muting;
|
||||||
pub use super::note::Entity as Note;
|
pub use super::note::Entity as Note;
|
||||||
pub use super::note_edit::Entity as NoteEdit;
|
pub use super::note_edit::Entity as NoteEdit;
|
||||||
pub use super::note_favorite::Entity as NoteFavorite;
|
pub use super::note_favorite::Entity as NoteFavorite;
|
||||||
|
pub use super::note_file::Entity as NoteFile;
|
||||||
pub use super::note_reaction::Entity as NoteReaction;
|
pub use super::note_reaction::Entity as NoteReaction;
|
||||||
pub use super::note_thread_muting::Entity as NoteThreadMuting;
|
pub use super::note_thread_muting::Entity as NoteThreadMuting;
|
||||||
pub use super::note_unread::Entity as NoteUnread;
|
pub use super::note_unread::Entity as NoteUnread;
|
||||||
|
|
|
@ -73,6 +73,7 @@ import { UserPending } from "@/models/entities/user-pending.js";
|
||||||
import { Webhook } from "@/models/entities/webhook.js";
|
import { Webhook } from "@/models/entities/webhook.js";
|
||||||
import { UserIp } from "@/models/entities/user-ip.js";
|
import { UserIp } from "@/models/entities/user-ip.js";
|
||||||
import { NoteEdit } from "@/models/entities/note-edit.js";
|
import { NoteEdit } from "@/models/entities/note-edit.js";
|
||||||
|
import { NoteFile } from "@/models/entities/note-file.js";
|
||||||
|
|
||||||
import { entities as charts } from "@/services/chart/entities.js";
|
import { entities as charts } from "@/services/chart/entities.js";
|
||||||
import { dbLogger } from "./logger.js";
|
import { dbLogger } from "./logger.js";
|
||||||
|
@ -143,6 +144,7 @@ export const entities = [
|
||||||
Note,
|
Note,
|
||||||
NoteEdit,
|
NoteEdit,
|
||||||
NoteFavorite,
|
NoteFavorite,
|
||||||
|
NoteFile,
|
||||||
NoteReaction,
|
NoteReaction,
|
||||||
NoteWatching,
|
NoteWatching,
|
||||||
NoteThreadMuting,
|
NoteThreadMuting,
|
||||||
|
|
|
@ -105,10 +105,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
||||||
`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read")`,
|
`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read")`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT IF NOT EXISTS "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_0d775946662d2575dfd2068a5f5" FOREIGN KEY ("antennaId") REFERENCES "antenna"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "antenna_note" ADD CONSTRAINT IF NOT EXISTS "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "antenna_note" ADD CONSTRAINT "FK_bd0397be22147e17210940e125b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_note_url"`);
|
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_note_url"`);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
|
@ -124,10 +124,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
||||||
`CREATE INDEX IF NOT EXISTS "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`,
|
`CREATE INDEX IF NOT EXISTS "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT IF NOT EXISTS "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "reversi_matching" ADD CONSTRAINT IF NOT EXISTS "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`,
|
`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`,
|
||||||
|
@ -139,10 +139,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
||||||
`CREATE INDEX IF NOT EXISTS "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt")`,
|
`CREATE INDEX IF NOT EXISTS "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt")`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT IF NOT EXISTS "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "reversi_game" ADD CONSTRAINT IF NOT EXISTS "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
);
|
);
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`,
|
`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class NoteFile1710304584214 implements MigrationInterface {
|
||||||
|
name = "NoteFile1710304584214";
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "note_file" (
|
||||||
|
"serialNo" bigserial PRIMARY KEY,
|
||||||
|
"noteId" varchar(32) NOT NULL,
|
||||||
|
"fileId" varchar(32) NOT NULL
|
||||||
|
)`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`
|
||||||
|
INSERT INTO "note_file" ("noteId", "fileId")
|
||||||
|
SELECT "t"."id", "t"."fid" FROM (
|
||||||
|
SELECT ROW_NUMBER() OVER () AS "rn", * FROM (
|
||||||
|
SELECT "id", UNNEST("fileIds") AS "fid" FROM "note"
|
||||||
|
) AS "s"
|
||||||
|
) AS "t"
|
||||||
|
INNER JOIN "drive_file" ON "drive_file"."id" = "t"."fid"
|
||||||
|
ORDER BY "rn"
|
||||||
|
`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "note_file" ADD FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "note_file" ADD FOREIGN KEY ("fileId") REFERENCES "drive_file" ("id") ON DELETE CASCADE`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_note_file_noteId" ON "note_file" ("noteId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_note_file_fileId" ON "note_file" ("fileId")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE "note_file"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class FixMutingIndices1710690239308 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_createdAt"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muteeId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muterId"`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_createdAt" ON "renote_muting" ("createdAt")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_muteeId" ON "renote_muting" ("muteeId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_muterId" ON "renote_muting" ("muterId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_createdAt" ON "reply_muting" ("createdAt")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_muteeId" ON "reply_muting" ("muteeId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_muterId" ON "reply_muting" ("muterId")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_createdAt"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muteeId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_renote_muting_muterId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_createdAt"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muteeId"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_reply_muting_muterId"`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_createdAt" ON "muting" ("createdAt")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_muteeId" ON "muting" ("muteeId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_reply_muting_muterId" ON "muting" ("muterId")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import { packedQueueCountSchema } from "@/models/schema/queue.js";
|
||||||
import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
|
import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js";
|
||||||
import { packedEmojiSchema } from "@/models/schema/emoji.js";
|
import { packedEmojiSchema } from "@/models/schema/emoji.js";
|
||||||
import { packedNoteEdit } from "@/models/schema/note-edit.js";
|
import { packedNoteEdit } from "@/models/schema/note-edit.js";
|
||||||
|
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
|
||||||
|
|
||||||
export const refs = {
|
export const refs = {
|
||||||
UserLite: packedUserLiteSchema,
|
UserLite: packedUserLiteSchema,
|
||||||
|
@ -47,6 +48,7 @@ export const refs = {
|
||||||
App: packedAppSchema,
|
App: packedAppSchema,
|
||||||
MessagingMessage: packedMessagingMessageSchema,
|
MessagingMessage: packedMessagingMessageSchema,
|
||||||
Note: packedNoteSchema,
|
Note: packedNoteSchema,
|
||||||
|
NoteFile: packedNoteFileSchema,
|
||||||
NoteEdit: packedNoteEdit,
|
NoteEdit: packedNoteEdit,
|
||||||
NoteReaction: packedNoteReactionSchema,
|
NoteReaction: packedNoteReactionSchema,
|
||||||
NoteFavorite: packedNoteFavoriteSchema,
|
NoteFavorite: packedNoteFavoriteSchema,
|
||||||
|
|
|
@ -4,12 +4,17 @@ import {
|
||||||
Index,
|
Index,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
Column,
|
Column,
|
||||||
|
ManyToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
type Relation,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import { id } from "../id.js";
|
import { id } from "../id.js";
|
||||||
|
import { Note } from "./note.js";
|
||||||
import { User } from "./user.js";
|
import { User } from "./user.js";
|
||||||
import { DriveFolder } from "./drive-folder.js";
|
import { DriveFolder } from "./drive-folder.js";
|
||||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
|
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
|
||||||
|
import { NoteFile } from "./note-file.js";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index(["userId", "folderId", "id"])
|
@Index(["userId", "folderId", "id"])
|
||||||
|
@ -31,12 +36,6 @@ export class DriveFile {
|
||||||
})
|
})
|
||||||
public userId: User["id"] | null;
|
public userId: User["id"] | null;
|
||||||
|
|
||||||
@ManyToOne((type) => User, {
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public user: User | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column("varchar", {
|
@Column("varchar", {
|
||||||
length: 512,
|
length: 512,
|
||||||
|
@ -171,12 +170,6 @@ export class DriveFile {
|
||||||
})
|
})
|
||||||
public folderId: DriveFolder["id"] | null;
|
public folderId: DriveFolder["id"] | null;
|
||||||
|
|
||||||
@ManyToOne((type) => DriveFolder, {
|
|
||||||
onDelete: "SET NULL",
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public folder: DriveFolder | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column("boolean", {
|
@Column("boolean", {
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -205,4 +198,30 @@ export class DriveFile {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public requestIp: string | null;
|
public requestIp: string | null;
|
||||||
|
|
||||||
|
//#region Relations
|
||||||
|
@OneToMany(
|
||||||
|
() => NoteFile,
|
||||||
|
(noteFile: NoteFile) => noteFile.file,
|
||||||
|
)
|
||||||
|
public noteFiles: Relation<NoteFile[]>;
|
||||||
|
|
||||||
|
@ManyToMany(
|
||||||
|
() => Note,
|
||||||
|
(note: Note) => note.files,
|
||||||
|
)
|
||||||
|
public notes: Relation<Note[]>;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, {
|
||||||
|
onDelete: "SET NULL",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: User | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => DriveFolder, {
|
||||||
|
onDelete: "SET NULL",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public folder: DriveFolder | null;
|
||||||
|
//#endregion Relations
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
Column,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
type Relation,
|
||||||
|
} from "typeorm";
|
||||||
|
import { Note } from "./note.js";
|
||||||
|
import { DriveFile } from "./drive-file.js";
|
||||||
|
import { id } from "../id.js";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class NoteFile {
|
||||||
|
@PrimaryGeneratedColumn("increment")
|
||||||
|
public serialNo: number;
|
||||||
|
|
||||||
|
@Index("IDX_note_file_noteId", { unique: false })
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public noteId: Note["id"];
|
||||||
|
|
||||||
|
@Index("IDX_note_file_fileId", { unique: false })
|
||||||
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
public fileId: DriveFile["id"];
|
||||||
|
|
||||||
|
//#region Relations
|
||||||
|
@ManyToOne(
|
||||||
|
() => Note,
|
||||||
|
(note: Note) => note.noteFiles,
|
||||||
|
)
|
||||||
|
public note: Relation<Note>;
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => DriveFile,
|
||||||
|
(file: DriveFile) => file.noteFiles,
|
||||||
|
)
|
||||||
|
public file: Relation<DriveFile>;
|
||||||
|
//#endregion Relations
|
||||||
|
}
|
|
@ -2,15 +2,20 @@ import {
|
||||||
Entity,
|
Entity,
|
||||||
Index,
|
Index,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
JoinTable,
|
||||||
Column,
|
Column,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
|
ManyToMany,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
type Relation,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import { User } from "./user.js";
|
import { User } from "./user.js";
|
||||||
import type { DriveFile } from "./drive-file.js";
|
import { DriveFile } from "./drive-file.js";
|
||||||
import { id } from "../id.js";
|
import { id } from "../id.js";
|
||||||
import { noteVisibilities } from "../../types.js";
|
import { noteVisibilities } from "../../types.js";
|
||||||
import { Channel } from "./channel.js";
|
import { Channel } from "./channel.js";
|
||||||
|
import { NoteFile } from "./note-file.js";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index("IDX_NOTE_TAGS", { synchronize: false })
|
@Index("IDX_NOTE_TAGS", { synchronize: false })
|
||||||
|
@ -34,12 +39,6 @@ export class Note {
|
||||||
})
|
})
|
||||||
public replyId: Note["id"] | null;
|
public replyId: Note["id"] | null;
|
||||||
|
|
||||||
@ManyToOne((type) => Note, {
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public reply: Note | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
@ -48,12 +47,6 @@ export class Note {
|
||||||
})
|
})
|
||||||
public renoteId: Note["id"] | null;
|
public renoteId: Note["id"] | null;
|
||||||
|
|
||||||
@ManyToOne((type) => Note, {
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public renote: Note | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column("varchar", {
|
@Column("varchar", {
|
||||||
length: 256,
|
length: 256,
|
||||||
|
@ -93,12 +86,6 @@ export class Note {
|
||||||
})
|
})
|
||||||
public userId: User["id"];
|
public userId: User["id"];
|
||||||
|
|
||||||
@ManyToOne((type) => User, {
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
})
|
|
||||||
@JoinColumn()
|
|
||||||
public user: User | null;
|
|
||||||
|
|
||||||
@Column("boolean", {
|
@Column("boolean", {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
@ -151,6 +138,8 @@ export class Note {
|
||||||
})
|
})
|
||||||
public score: number;
|
public score: number;
|
||||||
|
|
||||||
|
// FIXME: file id is not removed from this array even if the file is deleted
|
||||||
|
// TODO: drop this column and use note_files
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
|
@ -183,6 +172,7 @@ export class Note {
|
||||||
})
|
})
|
||||||
public mentions: User["id"][];
|
public mentions: User["id"][];
|
||||||
|
|
||||||
|
// FIXME: WHAT IS THIS
|
||||||
@Column("text", {
|
@Column("text", {
|
||||||
default: "[]",
|
default: "[]",
|
||||||
})
|
})
|
||||||
|
@ -216,12 +206,55 @@ export class Note {
|
||||||
})
|
})
|
||||||
public channelId: Channel["id"] | null;
|
public channelId: Channel["id"] | null;
|
||||||
|
|
||||||
@ManyToOne((type) => Channel, {
|
//#region Relations
|
||||||
|
@OneToMany(
|
||||||
|
() => NoteFile,
|
||||||
|
(noteFile: NoteFile) => noteFile.note,
|
||||||
|
)
|
||||||
|
public noteFiles: Relation<NoteFile[]>;
|
||||||
|
|
||||||
|
@ManyToMany(
|
||||||
|
() => DriveFile,
|
||||||
|
(file: DriveFile) => file.notes,
|
||||||
|
)
|
||||||
|
@JoinTable({
|
||||||
|
name: "note_file",
|
||||||
|
joinColumn: {
|
||||||
|
name: "noteId",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
inverseJoinColumn: {
|
||||||
|
name: "fileId",
|
||||||
|
referencedColumnName: "id",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
public files: Relation<DriveFile[]>;
|
||||||
|
|
||||||
|
@ManyToOne(() => Note, {
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public reply: Note | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Note, {
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public renote: Note | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Channel, {
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public channel: Channel | null;
|
public channel: Channel | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, {
|
||||||
|
onDelete: "CASCADE",
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: User | null;
|
||||||
|
//#endregion Relations
|
||||||
|
|
||||||
//#region Denormalized fields
|
//#region Denormalized fields
|
||||||
@Index()
|
@Index()
|
||||||
@Column("varchar", {
|
@Column("varchar", {
|
||||||
|
|
|
@ -66,12 +66,14 @@ import { InstanceRepository } from "./repositories/instance.js";
|
||||||
import { Webhook } from "./entities/webhook.js";
|
import { Webhook } from "./entities/webhook.js";
|
||||||
import { UserIp } from "./entities/user-ip.js";
|
import { UserIp } from "./entities/user-ip.js";
|
||||||
import { NoteEdit } from "./entities/note-edit.js";
|
import { NoteEdit } from "./entities/note-edit.js";
|
||||||
|
import { NoteFileRepository } from "./repositories/note-file.js";
|
||||||
|
|
||||||
export const Announcements = db.getRepository(Announcement);
|
export const Announcements = db.getRepository(Announcement);
|
||||||
export const AnnouncementReads = db.getRepository(AnnouncementRead);
|
export const AnnouncementReads = db.getRepository(AnnouncementRead);
|
||||||
export const Apps = AppRepository;
|
export const Apps = AppRepository;
|
||||||
export const Notes = NoteRepository;
|
export const Notes = NoteRepository;
|
||||||
export const NoteEdits = db.getRepository(NoteEdit);
|
export const NoteEdits = db.getRepository(NoteEdit);
|
||||||
|
export const NoteFiles = NoteFileRepository;
|
||||||
export const NoteFavorites = NoteFavoriteRepository;
|
export const NoteFavorites = NoteFavoriteRepository;
|
||||||
export const NoteWatchings = db.getRepository(NoteWatching);
|
export const NoteWatchings = db.getRepository(NoteWatching);
|
||||||
export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
|
export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { db } from "@/db/postgre.js";
|
||||||
|
import { NoteFile } from "@/models/entities/note-file.js";
|
||||||
|
|
||||||
|
export const NoteFileRepository = db.getRepository(NoteFile).extend({});
|
|
@ -0,0 +1,24 @@
|
||||||
|
export const packedNoteFileSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
serialNo: {
|
||||||
|
type: "number",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
noteId: {
|
||||||
|
type: "string",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
format: "id",
|
||||||
|
example: "xxxxxxxxxx",
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: "string",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
format: "id",
|
||||||
|
example: "xxxxxxxxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
|
@ -1,4 +1,3 @@
|
||||||
import { Brackets } from "typeorm";
|
|
||||||
import { Notes } from "@/models/index.js";
|
import { Notes } from "@/models/index.js";
|
||||||
import { Note } from "@/models/entities/note.js";
|
import { Note } from "@/models/entities/note.js";
|
||||||
import define from "@/server/api/define.js";
|
import define from "@/server/api/define.js";
|
||||||
|
@ -7,6 +6,7 @@ import { generateVisibilityQuery } from "@/server/api/common/generate-visibility
|
||||||
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
|
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
|
||||||
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
||||||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||||
|
import type { SelectQueryBuilder } from "typeorm";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -69,91 +69,123 @@ export const paramDef = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default define(meta, paramDef, async (ps, me) => {
|
export default define(meta, paramDef, async (ps, me) => {
|
||||||
const query = makePaginationQuery(
|
async function search(
|
||||||
Notes.createQueryBuilder("note"),
|
modifier?: (query: SelectQueryBuilder<Note>) => void,
|
||||||
ps.sinceId,
|
): Promise<Note[]> {
|
||||||
ps.untilId,
|
const query = makePaginationQuery(
|
||||||
ps.sinceDate ?? undefined,
|
Notes.createQueryBuilder("note"),
|
||||||
ps.untilDate ?? undefined,
|
ps.sinceId,
|
||||||
);
|
ps.untilId,
|
||||||
|
ps.sinceDate ?? undefined,
|
||||||
|
ps.untilDate ?? undefined,
|
||||||
|
);
|
||||||
|
modifier?.(query);
|
||||||
|
|
||||||
if (ps.userId != null) {
|
if (ps.userId != null) {
|
||||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.channelId != null) {
|
||||||
|
query.andWhere("note.channelId = :channelId", {
|
||||||
|
channelId: ps.channelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
query.innerJoinAndSelect("note.user", "user");
|
||||||
|
|
||||||
|
// "from: me": search all (public, home, followers, specified) my posts
|
||||||
|
// otherwise: search public indexable posts only
|
||||||
|
if (ps.userId == null || ps.userId !== me?.id) {
|
||||||
|
query
|
||||||
|
.andWhere("note.visibility = 'public'")
|
||||||
|
.andWhere("user.isIndexable = TRUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.userId != null) {
|
||||||
|
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.host === null) {
|
||||||
|
query.andWhere("note.userHost IS NULL");
|
||||||
|
}
|
||||||
|
if (ps.host != null) {
|
||||||
|
query.andWhere("note.userHost = :userHost", { userHost: ps.host });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.withFiles === true) {
|
||||||
|
query.andWhere("note.fileIds != '{}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
query
|
||||||
|
.leftJoinAndSelect("user.avatar", "avatar")
|
||||||
|
.leftJoinAndSelect("user.banner", "banner")
|
||||||
|
.leftJoinAndSelect("note.reply", "reply")
|
||||||
|
.leftJoinAndSelect("note.renote", "renote")
|
||||||
|
.leftJoinAndSelect("reply.user", "replyUser")
|
||||||
|
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
|
||||||
|
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
|
||||||
|
.leftJoinAndSelect("renote.user", "renoteUser")
|
||||||
|
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
||||||
|
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
|
||||||
|
|
||||||
|
generateVisibilityQuery(query, me);
|
||||||
|
if (me) generateMutedUserQuery(query, me);
|
||||||
|
if (me) generateBlockedUserQuery(query, me);
|
||||||
|
|
||||||
|
return await query.take(ps.limit).getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.channelId != null) {
|
let notes: Note[];
|
||||||
query.andWhere("note.channelId = :channelId", {
|
|
||||||
channelId: ps.channelId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.query != null) {
|
if (ps.query != null) {
|
||||||
const q = sqlLikeEscape(ps.query);
|
const q = sqlLikeEscape(ps.query);
|
||||||
|
|
||||||
if (ps.searchCwAndAlt) {
|
if (ps.searchCwAndAlt) {
|
||||||
query.andWhere(
|
// Whether we should return latest notes first
|
||||||
new Brackets((qb) => {
|
const isDescendingOrder =
|
||||||
qb.where("note.text &@~ :q", { q })
|
(ps.sinceId == null || ps.untilId != null) &&
|
||||||
.orWhere("note.cw &@~ :q", { q })
|
(ps.sinceId != null ||
|
||||||
.orWhere(
|
ps.untilId != null ||
|
||||||
`EXISTS (
|
ps.sinceDate == null ||
|
||||||
SELECT FROM "drive_file"
|
ps.untilDate != null);
|
||||||
WHERE
|
|
||||||
comment &@~ :q
|
const compare = isDescendingOrder
|
||||||
AND
|
? (lhs: Note, rhs: Note) =>
|
||||||
drive_file."id" = ANY(note."fileIds")
|
Math.sign(rhs.createdAt.getTime() - lhs.createdAt.getTime())
|
||||||
)`,
|
: (lhs: Note, rhs: Note) =>
|
||||||
{ q },
|
Math.sign(lhs.createdAt.getTime() - rhs.createdAt.getTime());
|
||||||
);
|
|
||||||
}),
|
notes = [
|
||||||
);
|
...new Map(
|
||||||
|
(
|
||||||
|
await Promise.all([
|
||||||
|
search((query) => {
|
||||||
|
query.andWhere("note.text &@~ :q", { q });
|
||||||
|
}),
|
||||||
|
search((query) => {
|
||||||
|
query.andWhere("note.cw &@~ :q", { q });
|
||||||
|
}),
|
||||||
|
search((query) => {
|
||||||
|
query
|
||||||
|
.andWhere("drive_file.comment &@~ :q", { q })
|
||||||
|
.innerJoin("note.files", "drive_file");
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.flatMap((e) => e)
|
||||||
|
.map((note) => [note.id, note]),
|
||||||
|
).values(),
|
||||||
|
]
|
||||||
|
.sort(compare)
|
||||||
|
.slice(0, ps.limit);
|
||||||
} else {
|
} else {
|
||||||
query.andWhere("note.text &@~ :q", { q });
|
notes = await search((query) => {
|
||||||
|
query.andWhere("note.text &@~ :q", { q });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
notes = await search();
|
||||||
}
|
}
|
||||||
|
|
||||||
query.innerJoinAndSelect("note.user", "user");
|
|
||||||
|
|
||||||
// "from: me": search all (public, home, followers, specified) my posts
|
|
||||||
// otherwise: search public indexable posts only
|
|
||||||
if (ps.userId == null || ps.userId !== me?.id) {
|
|
||||||
query
|
|
||||||
.andWhere("note.visibility = 'public'")
|
|
||||||
.andWhere("user.isIndexable = TRUE");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.userId != null) {
|
|
||||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.host === null) {
|
|
||||||
query.andWhere("note.userHost IS NULL");
|
|
||||||
}
|
|
||||||
if (ps.host != null) {
|
|
||||||
query.andWhere("note.userHost = :userHost", { userHost: ps.host });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.withFiles === true) {
|
|
||||||
query.andWhere("note.fileIds != '{}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
query
|
|
||||||
.leftJoinAndSelect("user.avatar", "avatar")
|
|
||||||
.leftJoinAndSelect("user.banner", "banner")
|
|
||||||
.leftJoinAndSelect("note.reply", "reply")
|
|
||||||
.leftJoinAndSelect("note.renote", "renote")
|
|
||||||
.leftJoinAndSelect("reply.user", "replyUser")
|
|
||||||
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
|
|
||||||
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
|
|
||||||
.leftJoinAndSelect("renote.user", "renoteUser")
|
|
||||||
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
|
||||||
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
|
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
|
||||||
if (me) generateMutedUserQuery(query, me);
|
|
||||||
if (me) generateBlockedUserQuery(query, me);
|
|
||||||
|
|
||||||
const notes: Note[] = await query.take(ps.limit).getMany();
|
|
||||||
|
|
||||||
return await Notes.packMany(notes, me);
|
return await Notes.packMany(notes, me);
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
Channels,
|
Channels,
|
||||||
ChannelFollowings,
|
ChannelFollowings,
|
||||||
NoteThreadMutings,
|
NoteThreadMutings,
|
||||||
|
NoteFiles,
|
||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||||
import type { App } from "@/models/entities/app.js";
|
import type { App } from "@/models/entities/app.js";
|
||||||
|
@ -343,6 +344,12 @@ export default async (
|
||||||
|
|
||||||
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
||||||
|
|
||||||
|
await NoteFiles.insert(
|
||||||
|
note.fileIds.map((fileId) => ({ noteId: note.id, fileId })),
|
||||||
|
).catch((e) => {
|
||||||
|
logger.error(inspect(e));
|
||||||
|
});
|
||||||
|
|
||||||
res(note);
|
res(note);
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
|
|
|
@ -40,7 +40,6 @@ import { i18n } from "@/i18n";
|
||||||
import { fetchInstance, instance } from "@/instance";
|
import { fetchInstance, instance } from "@/instance";
|
||||||
import { isSignedIn, me } from "@/me";
|
import { isSignedIn, me } from "@/me";
|
||||||
import { alert, api, confirm, popup, post, toast } from "@/os";
|
import { alert, api, confirm, popup, post, toast } from "@/os";
|
||||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
||||||
import { makeHotkey } from "@/scripts/hotkey";
|
import { makeHotkey } from "@/scripts/hotkey";
|
||||||
|
@ -246,11 +245,7 @@ function checkForSplash() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
// 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
||||||
if (
|
if (lastVersion < version && defaultStore.state.showUpdates) {
|
||||||
lastVersion != null &&
|
|
||||||
compareFirefishVersions(lastVersion, version) === 1 &&
|
|
||||||
defaultStore.state.showUpdates
|
|
||||||
) {
|
|
||||||
// ログインしてる場合だけ
|
// ログインしてる場合だけ
|
||||||
if (me) {
|
if (me) {
|
||||||
popup(
|
popup(
|
||||||
|
|
|
@ -85,7 +85,6 @@ import {
|
||||||
provideMetadataReceiver,
|
provideMetadataReceiver,
|
||||||
} from "@/scripts/page-metadata";
|
} from "@/scripts/page-metadata";
|
||||||
import icon from "@/scripts/icon";
|
import icon from "@/scripts/icon";
|
||||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
|
||||||
|
|
||||||
const isEmpty = (x: string | null) => x == null || x === "";
|
const isEmpty = (x: string | null) => x == null || x === "";
|
||||||
const el = ref<HTMLElement | null>(null);
|
const el = ref<HTMLElement | null>(null);
|
||||||
|
@ -122,8 +121,7 @@ os.api("admin/abuse-user-reports", {
|
||||||
|
|
||||||
if (defaultStore.state.showAdminUpdates) {
|
if (defaultStore.state.showAdminUpdates) {
|
||||||
os.api("latest-version").then((res) => {
|
os.api("latest-version").then((res) => {
|
||||||
updateAvailable.value =
|
updateAvailable.value = version < res?.latest_version;
|
||||||
compareFirefishVersions(version, res?.latest_version) === 1;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
const less = -1;
|
|
||||||
const same = 0;
|
|
||||||
const more = 1;
|
|
||||||
|
|
||||||
export const compareFirefishVersions = (
|
|
||||||
oldVersion: string,
|
|
||||||
newVersion: string,
|
|
||||||
) => {
|
|
||||||
if (oldVersion === newVersion) return same;
|
|
||||||
|
|
||||||
const o = oldVersion.split("-");
|
|
||||||
const n = newVersion.split("-");
|
|
||||||
|
|
||||||
if (o[0] < n[0]) return more;
|
|
||||||
if (o[0] === n[0] && o[1] == null && n[1] != null) return more;
|
|
||||||
if (o[0] === n[0] && o[1] != null && n[1] != null && o[1] < n[1]) return more;
|
|
||||||
|
|
||||||
return less;
|
|
||||||
};
|
|
|
@ -162,7 +162,6 @@ import { i18n } from "@/i18n";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import { version } from "@/config";
|
import { version } from "@/config";
|
||||||
import icon from "@/scripts/icon";
|
import icon from "@/scripts/icon";
|
||||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
|
||||||
|
|
||||||
const isEmpty = (x: string | null) => x == null || x === "";
|
const isEmpty = (x: string | null) => x == null || x === "";
|
||||||
|
|
||||||
|
@ -212,8 +211,7 @@ if (isAdmin) {
|
||||||
|
|
||||||
if (defaultStore.state.showAdminUpdates) {
|
if (defaultStore.state.showAdminUpdates) {
|
||||||
os.api("latest-version").then((res) => {
|
os.api("latest-version").then((res) => {
|
||||||
updateAvailable.value =
|
updateAvailable.value = version < res?.latest_version;
|
||||||
compareFirefishVersions(version, res?.latest_version) === 1;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue