Merge branch 'develop' into firefish-docs/develop
This commit is contained in:
commit
ff2221f951
26 changed files with 477 additions and 153 deletions
|
@ -1,6 +1,8 @@
|
|||
BEGIN;
|
||||
|
||||
DELETE FROM "migrations" WHERE name IN (
|
||||
'FixMutingIndices1710690239308',
|
||||
'NoteFile1710304584214',
|
||||
'RenameMetaColumns1705944717480',
|
||||
'SeparateHardMuteWordsAndPatterns1706413792769',
|
||||
'IndexAltTextAndCw1708872574733',
|
||||
|
@ -16,6 +18,20 @@ DELETE FROM "migrations" WHERE name IN (
|
|||
'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
|
||||
ALTER TABLE "meta" RENAME COLUMN "tosUrl" TO "ToSUrl";
|
||||
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'
|
||||
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
|
||||
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
|
||||
search for posts that literally contain the ID/URL."
|
||||
searchUsers: "Posted by (optional)"
|
||||
|
|
|
@ -1008,7 +1008,8 @@ enableTimelineStreaming: "タイムラインを自動で更新する"
|
|||
searchWords: "検索語句・照会するIDやURL"
|
||||
searchWordsDescription: "投稿を検索するには、ここに検索語句を入力してください。空白区切りでAND検索になり、ORを挟むと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が文字通り含まれる投稿を検索します。"
|
||||
searchUsers: "投稿元(オプション)"
|
||||
searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
|
||||
|
|
|
@ -1936,7 +1936,7 @@ moveFrom: 从旧账号迁移至此账号
|
|||
defaultReaction: 发出和收到帖子的默认表情符号反应
|
||||
sendModMail: 发送管理通知
|
||||
moderationNote: "管理笔记"
|
||||
ipFirstAcknowledged: "该日期是这个 IP 地址首次被获取到的日期"
|
||||
ipFirstAcknowledged: "首次获取此 IP 地址的日期"
|
||||
driveCapacityOverride: "网盘容量变更"
|
||||
isLocked: 该账号设置了关注请求
|
||||
_filters:
|
||||
|
@ -2047,11 +2047,12 @@ publishTimelines: 为访客发布时间线
|
|||
publishTimelinesDescription: 如果启用,在用户登出时本地和全局时间线也会显示在 {url} 上。
|
||||
searchWordsDescription: "要搜索帖子,请输入关键词。交集搜索关键词之间使用空格进行区分,并集搜索关键词之间使用 OR 进行区分。\n例如 '早上
|
||||
晚上' 将查找包含 '早上' 和 '晚上' 的帖子,而 '早上 OR 晚上' 将查找包含 '早上' 或 '晚上' (以及同时包含两者)的帖子。\n您还可以组合交集/并集条件,例如
|
||||
'(早上 OR 晚上) 困了' 。\n\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索”
|
||||
将搜索字面包含用户 ID/URL 的帖子。"
|
||||
'(早上 OR 晚上) 困了' 。\n如果您想搜索单词序列(例如一个英语句子),您必须将其放在双引号中,例如 \"Today I learned\" 以区分于交集搜索。\n
|
||||
\n如果您想转到特定的用户页面或帖子页面,请在此字段中输入用户 ID 或 URL,然后单击 “查询” 按钮。 单击 “搜索” 将搜索字面包含用户 ID/URL
|
||||
的帖子。"
|
||||
searchRangeDescription: "如果您要过滤时间段,请按以下格式输入:20220615-20231031\n\n如果您省略年份(例如 0105-0106
|
||||
或 20231105-0110),它将被解释为当前年份。\n\n您还可以省略开始日期或结束日期。 例如 -0102 将过滤搜索结果以仅显示今年 1 月 2 日之前发布的帖子,而
|
||||
20231026- 将过滤结果以仅显示 2023 年 10 月 26 日之后发布的帖子。"
|
||||
messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。"
|
||||
noAltTextWarning: 有些附件没有说明。您是否忘记写说明了?
|
||||
showNoAltTextWarning: 当您尝试发布没有说明的帖子附件时显示警告
|
||||
noAltTextWarning: 有些附件没有描述。您是否忘记写描述了?
|
||||
showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告
|
||||
|
|
|
@ -64,6 +64,8 @@ pub enum Relation {
|
|||
DriveFolder,
|
||||
#[sea_orm(has_many = "super::messaging_message::Entity")]
|
||||
MessagingMessage,
|
||||
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||
NoteFile,
|
||||
#[sea_orm(has_many = "super::page::Entity")]
|
||||
Page,
|
||||
#[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 {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Page.def()
|
||||
|
|
|
@ -35,6 +35,7 @@ pub mod muting;
|
|||
pub mod note;
|
||||
pub mod note_edit;
|
||||
pub mod note_favorite;
|
||||
pub mod note_file;
|
||||
pub mod note_reaction;
|
||||
pub mod note_thread_muting;
|
||||
pub mod note_unread;
|
||||
|
|
|
@ -38,8 +38,6 @@ pub struct Model {
|
|||
#[sea_orm(column_name = "visibleUserIds")]
|
||||
pub visible_user_ids: Vec<String>,
|
||||
pub mentions: Vec<String>,
|
||||
#[sea_orm(column_name = "mentionedRemoteUsers", column_type = "Text")]
|
||||
pub mentioned_remote_users: String,
|
||||
pub emojis: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
#[sea_orm(column_name = "hasPoll")]
|
||||
|
@ -100,6 +98,8 @@ pub enum Relation {
|
|||
NoteEdit,
|
||||
#[sea_orm(has_many = "super::note_favorite::Entity")]
|
||||
NoteFavorite,
|
||||
#[sea_orm(has_many = "super::note_file::Entity")]
|
||||
NoteFile,
|
||||
#[sea_orm(has_many = "super::note_reaction::Entity")]
|
||||
NoteReaction,
|
||||
#[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 {
|
||||
fn to() -> RelationDef {
|
||||
Relation::NoteReaction.def()
|
||||
|
|
48
packages/backend-rs/src/model/entity/note_file.rs
Normal file
48
packages/backend-rs/src/model/entity/note_file.rs
Normal file
|
@ -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_edit::Entity as NoteEdit;
|
||||
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_thread_muting::Entity as NoteThreadMuting;
|
||||
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 { UserIp } from "@/models/entities/user-ip.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 { dbLogger } from "./logger.js";
|
||||
|
@ -143,6 +144,7 @@ export const entities = [
|
|||
Note,
|
||||
NoteEdit,
|
||||
NoteFavorite,
|
||||
NoteFile,
|
||||
NoteReaction,
|
||||
NoteWatching,
|
||||
NoteThreadMuting,
|
||||
|
|
|
@ -105,10 +105,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
`CREATE INDEX "IDX_9937ea48d7ae97ffb4f3f063a4" ON "antenna_note" ("read")`,
|
||||
);
|
||||
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(
|
||||
`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(
|
||||
|
@ -124,10 +124,10 @@ export class RemoveNativeUtilsMigration1705877093218
|
|||
`CREATE INDEX IF NOT EXISTS "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`,
|
||||
);
|
||||
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(
|
||||
`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(
|
||||
`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")`,
|
||||
);
|
||||
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(
|
||||
`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(
|
||||
`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`,
|
||||
|
|
41
packages/backend/src/migration/1710304584214-note-file.ts
Normal file
41
packages/backend/src/migration/1710304584214-note-file.ts
Normal file
|
@ -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 { packedEmojiSchema } from "@/models/schema/emoji.js";
|
||||
import { packedNoteEdit } from "@/models/schema/note-edit.js";
|
||||
import { packedNoteFileSchema } from "@/models/schema/note-file.js";
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
@ -47,6 +48,7 @@ export const refs = {
|
|||
App: packedAppSchema,
|
||||
MessagingMessage: packedMessagingMessageSchema,
|
||||
Note: packedNoteSchema,
|
||||
NoteFile: packedNoteFileSchema,
|
||||
NoteEdit: packedNoteEdit,
|
||||
NoteReaction: packedNoteReactionSchema,
|
||||
NoteFavorite: packedNoteFavoriteSchema,
|
||||
|
|
|
@ -4,12 +4,17 @@ import {
|
|||
Index,
|
||||
JoinColumn,
|
||||
Column,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
type Relation,
|
||||
} from "typeorm";
|
||||
import { id } from "../id.js";
|
||||
import { Note } from "./note.js";
|
||||
import { User } from "./user.js";
|
||||
import { DriveFolder } from "./drive-folder.js";
|
||||
import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
|
||||
import { NoteFile } from "./note-file.js";
|
||||
|
||||
@Entity()
|
||||
@Index(["userId", "folderId", "id"])
|
||||
|
@ -31,12 +36,6 @@ export class DriveFile {
|
|||
})
|
||||
public userId: User["id"] | null;
|
||||
|
||||
@ManyToOne((type) => User, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
length: 512,
|
||||
|
@ -171,12 +170,6 @@ export class DriveFile {
|
|||
})
|
||||
public folderId: DriveFolder["id"] | null;
|
||||
|
||||
@ManyToOne((type) => DriveFolder, {
|
||||
onDelete: "SET NULL",
|
||||
})
|
||||
@JoinColumn()
|
||||
public folder: DriveFolder | null;
|
||||
|
||||
@Index()
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
|
@ -205,4 +198,30 @@ export class DriveFile {
|
|||
nullable: true,
|
||||
})
|
||||
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
|
||||
}
|
||||
|
|
45
packages/backend/src/models/entities/note-file.ts
Normal file
45
packages/backend/src/models/entities/note-file.ts
Normal file
|
@ -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,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
Column,
|
||||
PrimaryColumn,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
type Relation,
|
||||
} from "typeorm";
|
||||
import { User } from "./user.js";
|
||||
import type { DriveFile } from "./drive-file.js";
|
||||
import { DriveFile } from "./drive-file.js";
|
||||
import { id } from "../id.js";
|
||||
import { noteVisibilities } from "../../types.js";
|
||||
import { Channel } from "./channel.js";
|
||||
import { NoteFile } from "./note-file.js";
|
||||
|
||||
@Entity()
|
||||
@Index("IDX_NOTE_TAGS", { synchronize: false })
|
||||
|
@ -34,12 +39,6 @@ export class Note {
|
|||
})
|
||||
public replyId: Note["id"] | null;
|
||||
|
||||
@ManyToOne((type) => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public reply: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@ -48,12 +47,6 @@ export class Note {
|
|||
})
|
||||
public renoteId: Note["id"] | null;
|
||||
|
||||
@ManyToOne((type) => Note, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public renote: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
length: 256,
|
||||
|
@ -93,12 +86,6 @@ export class Note {
|
|||
})
|
||||
public userId: User["id"];
|
||||
|
||||
@ManyToOne((type) => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column("boolean", {
|
||||
default: false,
|
||||
})
|
||||
|
@ -151,6 +138,8 @@ export class Note {
|
|||
})
|
||||
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()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@ -183,6 +172,7 @@ export class Note {
|
|||
})
|
||||
public mentions: User["id"][];
|
||||
|
||||
// FIXME: WHAT IS THIS
|
||||
@Column("text", {
|
||||
default: "[]",
|
||||
})
|
||||
|
@ -216,12 +206,55 @@ export class Note {
|
|||
})
|
||||
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",
|
||||
})
|
||||
@JoinColumn()
|
||||
public channel: Channel | null;
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
//#endregion Relations
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column("varchar", {
|
||||
|
|
|
@ -66,12 +66,14 @@ import { InstanceRepository } from "./repositories/instance.js";
|
|||
import { Webhook } from "./entities/webhook.js";
|
||||
import { UserIp } from "./entities/user-ip.js";
|
||||
import { NoteEdit } from "./entities/note-edit.js";
|
||||
import { NoteFileRepository } from "./repositories/note-file.js";
|
||||
|
||||
export const Announcements = db.getRepository(Announcement);
|
||||
export const AnnouncementReads = db.getRepository(AnnouncementRead);
|
||||
export const Apps = AppRepository;
|
||||
export const Notes = NoteRepository;
|
||||
export const NoteEdits = db.getRepository(NoteEdit);
|
||||
export const NoteFiles = NoteFileRepository;
|
||||
export const NoteFavorites = NoteFavoriteRepository;
|
||||
export const NoteWatchings = db.getRepository(NoteWatching);
|
||||
export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
|
||||
|
|
4
packages/backend/src/models/repositories/note-file.ts
Normal file
4
packages/backend/src/models/repositories/note-file.ts
Normal file
|
@ -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({});
|
24
packages/backend/src/models/schema/note-file.ts
Normal file
24
packages/backend/src/models/schema/note-file.ts
Normal file
|
@ -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 { Note } from "@/models/entities/note.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 { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
||||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||
import type { SelectQueryBuilder } from "typeorm";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -69,91 +69,123 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
ps.sinceDate ?? undefined,
|
||||
ps.untilDate ?? undefined,
|
||||
);
|
||||
async function search(
|
||||
modifier?: (query: SelectQueryBuilder<Note>) => void,
|
||||
): Promise<Note[]> {
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
ps.sinceDate ?? undefined,
|
||||
ps.untilDate ?? undefined,
|
||||
);
|
||||
modifier?.(query);
|
||||
|
||||
if (ps.userId != null) {
|
||||
query.andWhere("note.userId = :userId", { userId: ps.userId });
|
||||
if (ps.userId != null) {
|
||||
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) {
|
||||
query.andWhere("note.channelId = :channelId", {
|
||||
channelId: ps.channelId,
|
||||
});
|
||||
}
|
||||
let notes: Note[];
|
||||
|
||||
if (ps.query != null) {
|
||||
const q = sqlLikeEscape(ps.query);
|
||||
|
||||
if (ps.searchCwAndAlt) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.text &@~ :q", { q })
|
||||
.orWhere("note.cw &@~ :q", { q })
|
||||
.orWhere(
|
||||
`EXISTS (
|
||||
SELECT FROM "drive_file"
|
||||
WHERE
|
||||
comment &@~ :q
|
||||
AND
|
||||
drive_file."id" = ANY(note."fileIds")
|
||||
)`,
|
||||
{ q },
|
||||
);
|
||||
}),
|
||||
);
|
||||
// Whether we should return latest notes first
|
||||
const isDescendingOrder =
|
||||
(ps.sinceId == null || ps.untilId != null) &&
|
||||
(ps.sinceId != null ||
|
||||
ps.untilId != null ||
|
||||
ps.sinceDate == null ||
|
||||
ps.untilDate != null);
|
||||
|
||||
const compare = isDescendingOrder
|
||||
? (lhs: Note, rhs: Note) =>
|
||||
Math.sign(rhs.createdAt.getTime() - lhs.createdAt.getTime())
|
||||
: (lhs: Note, rhs: Note) =>
|
||||
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 {
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
Channels,
|
||||
ChannelFollowings,
|
||||
NoteThreadMutings,
|
||||
NoteFiles,
|
||||
} from "@/models/index.js";
|
||||
import type { DriveFile } from "@/models/entities/drive-file.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);
|
||||
|
||||
await NoteFiles.insert(
|
||||
note.fileIds.map((fileId) => ({ noteId: note.id, fileId })),
|
||||
).catch((e) => {
|
||||
logger.error(inspect(e));
|
||||
});
|
||||
|
||||
res(note);
|
||||
|
||||
// Register host
|
||||
|
|
|
@ -40,7 +40,6 @@ import { i18n } from "@/i18n";
|
|||
import { fetchInstance, instance } from "@/instance";
|
||||
import { isSignedIn, me } from "@/me";
|
||||
import { alert, api, confirm, popup, post, toast } from "@/os";
|
||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
||||
import { deviceKind } from "@/scripts/device-kind";
|
||||
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
||||
import { makeHotkey } from "@/scripts/hotkey";
|
||||
|
@ -246,11 +245,7 @@ function checkForSplash() {
|
|||
|
||||
try {
|
||||
// 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
||||
if (
|
||||
lastVersion != null &&
|
||||
compareFirefishVersions(lastVersion, version) === 1 &&
|
||||
defaultStore.state.showUpdates
|
||||
) {
|
||||
if (lastVersion < version && defaultStore.state.showUpdates) {
|
||||
// ログインしてる場合だけ
|
||||
if (me) {
|
||||
popup(
|
||||
|
|
|
@ -85,7 +85,6 @@ import {
|
|||
provideMetadataReceiver,
|
||||
} from "@/scripts/page-metadata";
|
||||
import icon from "@/scripts/icon";
|
||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
||||
|
||||
const isEmpty = (x: string | null) => x == null || x === "";
|
||||
const el = ref<HTMLElement | null>(null);
|
||||
|
@ -122,8 +121,7 @@ os.api("admin/abuse-user-reports", {
|
|||
|
||||
if (defaultStore.state.showAdminUpdates) {
|
||||
os.api("latest-version").then((res) => {
|
||||
updateAvailable.value =
|
||||
compareFirefishVersions(version, res?.latest_version) === 1;
|
||||
updateAvailable.value = version < res?.latest_version;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 { version } from "@/config";
|
||||
import icon from "@/scripts/icon";
|
||||
import { compareFirefishVersions } from "@/scripts/compare-versions";
|
||||
|
||||
const isEmpty = (x: string | null) => x == null || x === "";
|
||||
|
||||
|
@ -212,8 +211,7 @@ if (isAdmin) {
|
|||
|
||||
if (defaultStore.state.showAdminUpdates) {
|
||||
os.api("latest-version").then((res) => {
|
||||
updateAvailable.value =
|
||||
compareFirefishVersions(version, res?.latest_version) === 1;
|
||||
updateAvailable.value = version < res?.latest_version;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue