refactor (backend): add note_file table to store (noteId, fileId) pairs
Co-authored-by: sup39 <dev@sup39.dev>
This commit is contained in:
parent
0f880b55e3
commit
2220d5c56e
16 changed files with 279 additions and 32 deletions
|
@ -1,6 +1,7 @@
|
|||
BEGIN;
|
||||
|
||||
DELETE FROM "migrations" WHERE name IN (
|
||||
'NoteFile1710304584214',
|
||||
'RenameMetaColumns1705944717480',
|
||||
'SeparateHardMuteWordsAndPatterns1706413792769',
|
||||
'IndexAltTextAndCw1708872574733',
|
||||
|
@ -16,6 +17,9 @@ DELETE FROM "migrations" WHERE name IN (
|
|||
'RemoveNativeUtilsMigration1705877093218'
|
||||
);
|
||||
|
||||
-- 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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -100,6 +100,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 +166,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,
|
||||
|
|
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 "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"`);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
|
@ -152,6 +139,7 @@ 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(),
|
||||
|
@ -218,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;
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue