1
0
Fork 1
mirror of https://example.com synced 2024-11-22 15:56:38 +09:00

refactor (backend): add note_file table to store (noteId, fileId)

This commit is contained in:
naskya 2024-03-13 17:02:05 +09:00
parent a12ac93a41
commit bc5f233b9a
Signed by: naskya
GPG key ID: 712D413B3A9FED5C
18 changed files with 161 additions and 36 deletions

View file

@ -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;

View file

@ -0,0 +1,20 @@
//! `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")]
#[napi_derive::napi(object, js_name = "NoteFile", use_nullable = true)]
pub struct Model {
#[sea_orm(column_name = "idNum", primary_key)]
pub id_num: 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 {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -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;

View file

@ -145,8 +145,8 @@ pub enum UserEmojimodpermEnum {
Full, Full,
#[sea_orm(string_value = "mod")] #[sea_orm(string_value = "mod")]
Mod, Mod,
#[sea_orm(string_value = "unauthorized")] #[sea_orm(string_value = "none")]
Unauthorized, None,
} }
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[napi_derive::napi] #[napi_derive::napi]

View file

@ -60,7 +60,6 @@ pub struct Model {
pub receive_announcement_email: bool, pub receive_announcement_email: bool,
#[sea_orm(column_name = "emailNotificationTypes", column_type = "JsonBinary")] #[sea_orm(column_name = "emailNotificationTypes", column_type = "JsonBinary")]
pub email_notification_types: Json, pub email_notification_types: Json,
pub lang: Option<String>,
#[sea_orm(column_name = "mutedInstances", column_type = "JsonBinary")] #[sea_orm(column_name = "mutedInstances", column_type = "JsonBinary")]
pub muted_instances: Json, pub muted_instances: Json,
#[sea_orm(column_name = "publicReactions")] #[sea_orm(column_name = "publicReactions")]

View file

@ -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,

View file

@ -13,22 +13,9 @@ export class Pgroonga1698420787202 implements MigrationInterface {
); );
} }
<<<<<<<< HEAD:packages/backend/migration-neko/1698420787202-pgroonga.js
async down(queryRunner) {
await queryRunner.query(
`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`,
);
========
async down(queryRunner: QueryRunner): Promise<void> { async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_fcb770976ff8240af5799e3ffc"`); await queryRunner.query(`DROP INDEX "IDX_fcb770976ff8240af5799e3ffc"`);
await queryRunner.query(`DROP INDEX "IDX_065d4d8f3b5adb4a08841eae3c"`); await queryRunner.query(`DROP INDEX "IDX_065d4d8f3b5adb4a08841eae3c"`);
await queryRunner.query(`DROP INDEX "IDX_f27f5d88941e57442be75ba9c8"`); await queryRunner.query(`DROP INDEX "IDX_f27f5d88941e57442be75ba9c8"`);
>>>>>>>> bf9916740 (refactor (backend): move migrations inside backend/src):packages/backend/src/migration/1698420787202-pgroonga.ts
} }
} }

View file

@ -1,15 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class DropUserProfileLanguage1708452631156
implements MigrationInterface
{
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "lang"`);
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user_profile" ADD COLUMN "lang" character varying(32)`,
);
}
}

View file

@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner, TableIndex } from "typeorm";
export class NoteFile1710304584214 implements MigrationInterface {
name = "NoteFile1710304584214";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "note_file" ("idNum" bigserial PRIMARY KEY, "noteId" varchar(32) NOT NULL, "fileId" varchar(32) NOT NULL)`,
);
await queryRunner.query(`
INSERT INTO "note_file" ("noteId", "fileId") SELECT * FROM (
SELECT "id", UNNEST("fileIds") FROM "note"
)
`);
await queryRunner.createIndices("note_file", [
{
name: "IDX_note_file_noteId",
columnNames: ["noteId"],
isUnique: false,
},
{
name: "IDX_note_file_fileId",
columnNames: ["fileId"],
isUnique: false,
},
] as TableIndex[]);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("note_file");
}
}

View file

@ -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,

View file

@ -0,0 +1,25 @@
import { Entity, Index, Column, PrimaryGeneratedColumn } from "typeorm";
import { Note } from "./note.js";
import { DriveFile } from "./drive-file.js";
import { id } from "../id.js";
@Entity()
export class NoteFile {
// We don't use this column but TypeORM requires a primary column
@PrimaryGeneratedColumn("increment")
public idNum: 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"];
}

View file

@ -151,6 +151,7 @@ export class Note {
}) })
public score: number; public score: number;
// TODO: drop this column and use note_files
@Index() @Index()
@Column({ @Column({
...id(), ...id(),

View file

@ -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);

View 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({});

View file

@ -0,0 +1,19 @@
export const packedNoteFileSchema = {
type: "object",
properties: {
noteId: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx",
},
fileId: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx",
},
},
} as const;

View file

@ -1,10 +1,14 @@
import type { DriveFile } from "@/models/entities/drive-file.js"; import type { DriveFile } from "@/models/entities/drive-file.js";
import { InternalStorage } from "./internal-storage.js"; import { InternalStorage } from "./internal-storage.js";
import { DriveFiles } from "@/models/index.js"; import { DriveFiles, NoteFiles } from "@/models/index.js";
import { createDeleteObjectStorageFileJob } from "@/queue/index.js"; import { createDeleteObjectStorageFileJob } from "@/queue/index.js";
import { fetchMeta } from "@/misc/backend-rs.js"; import { fetchMeta } from "@/misc/backend-rs.js";
import { getS3 } from "./s3.js"; import { getS3 } from "./s3.js";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import Logger from "@/services/logger.js";
import { inspect } from "node:util";
const logger = new Logger("delete-file");
export async function deleteFile(file: DriveFile, isExpired = false) { export async function deleteFile(file: DriveFile, isExpired = false) {
if (file.storedInternal) { if (file.storedInternal) {
@ -79,6 +83,15 @@ async function postProcess(file: DriveFile, isExpired = false) {
} else { } else {
DriveFiles.delete(file.id); DriveFiles.delete(file.id);
} }
const noteFiles = await NoteFiles.findBy({ fileId: file.id });
await Promise.all(
noteFiles.map(async (noteFile) => {
await NoteFiles.delete(noteFile);
}),
).catch((e) => {
logger.error(inspect(e));
});
} }
export async function deleteObjectStorageFile(key: string) { export async function deleteObjectStorageFile(key: string) {

View file

@ -33,6 +33,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";
@ -342,6 +343,14 @@ export default async (
const note = await insertNote(user, data, tags, emojis, mentionedUsers); const note = await insertNote(user, data, tags, emojis, mentionedUsers);
await Promise.all(
note.fileIds.map(async (fileId) => {
await NoteFiles.insert({ noteId: note.id, fileId });
}),
).catch((e) => {
logger.error(inspect(e));
});
res(note); res(note);
// Register host // Register host

View file

@ -8,7 +8,7 @@ import renderTombstone from "@/remote/activitypub/renderer/tombstone.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js"; import type { Note, IMentionedRemoteUsers } from "@/models/entities/note.js";
import { Notes, Users, Instances } from "@/models/index.js"; import { Notes, NoteFiles, Users, Instances } from "@/models/index.js";
import { import {
deliverToFollowers, deliverToFollowers,
deliverToUser, deliverToUser,
@ -16,7 +16,10 @@ import {
import { hasOtherRenoteOfThisNote } from "@/misc/backend-rs.js"; import { hasOtherRenoteOfThisNote } from "@/misc/backend-rs.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { deliverToRelays } from "@/services/relay.js"; import { deliverToRelays } from "@/services/relay.js";
// import meilisearch from "@/db/meilisearch.js"; import Logger from "@/services/logger.js";
import { inspect } from "node:util";
const logger = new Logger("delete-note");
/** /**
* 稿 * 稿
@ -87,11 +90,23 @@ export default async function (
deliverToConcerned(user, note, content); deliverToConcerned(user, note, content);
} }
const cascadingNotes = await findCascadingNotes(note);
for await (const cascadingNote of cascadingNotes) {
await Promise.all(
cascadingNote.fileIds.map(async (fileId) => {
await NoteFiles.delete({ noteId: cascadingNote.id, fileId });
}),
).catch((e) => {
logger.error(inspect(e));
});
}
// also deliever delete activity to cascaded notes // also deliever delete activity to cascaded notes
const cascadingNotes = (await findCascadingNotes(note)).filter( const cascadingFederatedNotes = cascadingNotes.filter(
(note) => !note.localOnly, (note) => !note.localOnly,
); // filter out local-only notes ); // filter out local-only notes
for (const cascadingNote of cascadingNotes) { for (const cascadingNote of cascadingFederatedNotes) {
if (!cascadingNote.user) continue; if (!cascadingNote.user) continue;
if (!Users.isLocalUser(cascadingNote.user)) continue; if (!Users.isLocalUser(cascadingNote.user)) continue;
const content = renderActivity( const content = renderActivity(
@ -116,6 +131,14 @@ export default async function (
id: note.id, id: note.id,
userId: user.id, userId: user.id,
}); });
await Promise.all(
note.fileIds.map(async (fileId) => {
await NoteFiles.delete({ noteId: note.id, fileId });
}),
).catch((e) => {
logger.error(inspect(e));
});
} }
// if (meilisearch) { // if (meilisearch) {