Skip to content

Commit

Permalink
WIP: 노트수정 히스토리
Browse files Browse the repository at this point in the history
- DB에 새로운 키들 추가
- 마이그레이션 새로 만들었다
- API에서 나올 JSON스키마 설계 (초안)
- NoteHistoryEntityService (초안)
- NoteUpdateService 에서 NoteHistory를 기록하도록 호출 추가
  • Loading branch information
yunochi committed Sep 3, 2024
1 parent 2f88f32 commit 7381d71
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/


export class NoteHistory1725267809954 {
name = 'NoteHistory1725267809954';
export class NoteHistory1725325908094 {
name = 'NoteHistory1725325908094';

async up(queryRunner) {
await queryRunner.query('CREATE TABLE "note_history" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "fileIds" character varying(32) array NOT NULL DEFAULT \'{}\', "attachedFileTypes" character varying(256) array NOT NULL DEFAULT \'{}\', "emojis" character varying(128) array NOT NULL DEFAULT \'{}\', "noteIdId" character varying(32), "userIdId" character varying(32), CONSTRAINT "PK_b8603c8aa42b803c7687e52c2c0" PRIMARY KEY ("id")); COMMENT ON COLUMN "note_history"."noteId" IS \'The target Note ID for history\'');
await queryRunner.query('CREATE TYPE "public"."note_history_visibility_enum" AS ENUM(\'public\', \'home\', \'followers\', \'specified\')');
await queryRunner.query('CREATE TABLE "note_history" ("id" character varying(32) NOT NULL, "noteId" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "fileIds" character varying(32) array NOT NULL DEFAULT \'{}\', "attachedFileTypes" character varying(256) array NOT NULL DEFAULT \'{}\', "emojis" character varying(128) array NOT NULL DEFAULT \'{}\', "text" text, "visibility" "public"."note_history_visibility_enum" NOT NULL, "visibleUserIds" character varying(32) array NOT NULL DEFAULT \'{}\', "noteIdId" character varying(32), "userIdId" character varying(32), CONSTRAINT "PK_b8603c8aa42b803c7687e52c2c0" PRIMARY KEY ("id")); COMMENT ON COLUMN "note_history"."noteId" IS \'The target Note ID for history\'');
await queryRunner.query('CREATE INDEX "IDX_1e2492ba7582bf830b750a2962" ON "note_history" ("noteId") ');
await queryRunner.query('ALTER TABLE "note_history" ADD CONSTRAINT "FK_087d222cdeb6ed35b7c0ae7a67f" FOREIGN KEY ("noteIdId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION');
await queryRunner.query('ALTER TABLE "note_history" ADD CONSTRAINT "FK_31ef25f0b13c2791ae74f895ee2" FOREIGN KEY ("userIdId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION');
Expand All @@ -19,5 +19,6 @@ export class NoteHistory1725267809954 {
await queryRunner.query('ALTER TABLE "note_history" DROP CONSTRAINT "FK_087d222cdeb6ed35b7c0ae7a67f"');
await queryRunner.query('DROP INDEX "public"."IDX_1e2492ba7582bf830b750a2962"');
await queryRunner.query('DROP TABLE "note_history"');
await queryRunner.query('DROP TYPE "public"."note_history_visibility_enum"');
}
};
6 changes: 5 additions & 1 deletion packages/backend/src/core/NoteHistoryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class NoteHistorySerivce implements OnApplicationShutdown {
}

@bindThis
public async record (
public async recordHistory (
note_id: MiNote['id'],
note: MiNote,
optoins: Option,
Expand All @@ -86,7 +86,11 @@ export class NoteHistorySerivce implements OnApplicationShutdown {
fileIds: note.fileIds,
attachedFileTypes: note.attachedFileTypes,
emojis: note.emojis,
text: note.text,
visibility: note.visibility,
visibleUserIds: note.visibleUserIds,
};

try {
this.noteHistoryRepository.insert(history_data);
} catch (e) {
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/core/NoteUpdateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import Logger from '@/logger.js';
import { NoteEntityService } from './entities/NoteEntityService.js';
import { LoggerService } from './LoggerService.js';
import { NoteHistorySerivce } from './NoteHistoryService.js';

type MinimumUser = {
id: MiUser['id'];
Expand Down Expand Up @@ -75,6 +76,7 @@ export class NoteUpdateService implements OnApplicationShutdown {
private searchService: SearchService,
private activeUsersChart: ActiveUsersChart,
private loggerService: LoggerService,
private noteHistoryService: NoteHistorySerivce,
) {
this.logger = this.loggerService.getLogger('NoteUpdateService');
}
Expand Down Expand Up @@ -203,6 +205,8 @@ export class NoteUpdateService implements OnApplicationShutdown {
await this.notesRepository.update({ id: note.id }, values);
}

await this.noteHistoryService.recordHistory(note.id, note, { updatedAt: data.updatedAt });

return await this.notesRepository.findOneBy({ id: note.id });
} catch (e) {
this.logger.error(`${JSON.stringify(e)}`);
Expand Down
114 changes: 114 additions & 0 deletions packages/backend/src/core/entities/NoteHistoryEntityService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, NoteHistoryRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { DebounceLoader } from '@/misc/loader.js';
import { IdService } from '@/core/IdService.js';
import { NoteHistory } from '@/models/NoteHistory.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js';

@Injectable()
export class NoteHistoryEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
private driveFileEntityService: DriveFileEntityService;
private customEmojiService: CustomEmojiService;
private idService: IdService;
private noteLoader = new DebounceLoader(this.findNoteOrFail);

constructor(
private moduleRef: ModuleRef,

@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

@Inject(DI.notesRepository)
private notesRepository: NotesRepository,

@Inject(DI.noteHistoryRepository)
private noteHistoryRepository: NoteHistoryRepository,

@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

//private userEntityService: UserEntityService,
//private driveFileEntityService: DriveFileEntityService,
//private customEmojiService: CustomEmojiService,
//private reactionService: ReactionService,
) {
}

onModuleInit() {
this.userEntityService = this.moduleRef.get('UserEntityService');
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.idService = this.moduleRef.get('IdService');
}

@bindThis
public async packAttachedFiles(fileIds: NoteHistory['fileIds'], packedFiles: Map<NoteHistory['fileIds'][number], Packed<'DriveFile'> | null>): Promise<Packed<'DriveFile'>[]> {
const missingIds = [];
for (const id of fileIds) {
if (!packedFiles.has(id)) missingIds.push(id);
}
if (missingIds.length) {
const additionalMap = await this.driveFileEntityService.packManyByIdsMap(missingIds);
for (const [k, v] of additionalMap) {
packedFiles.set(k, v);
}
}
return fileIds.map(id => packedFiles.get(id)).filter(x => x != null);
}

@bindThis
public async pack(
src: NoteHistory['id'],
host: MiNote['userHost'],
options?: {
_hint_?: {
packedFiles: Map<NoteHistory['fileIds'][number], Packed<'DriveFile'> | null>;
};
},
): Promise<Packed<'NoteHistory'>> {
const note = await this.noteLoader.load(src);

const text = note.text;
const packedFiles = options?._hint_?.packedFiles;

const packed: Packed<'NoteHistory'> = await awaitAll({
id: note.id,
noteId: note.noteId,
updatedAt: note.updatedAt.toISOString(),
userId: note.userId,
text: text,
visibility: note.visibility,
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
fileIds: note.fileIds,
files: packedFiles != null ? this.packAttachedFiles(note.fileIds, packedFiles) : this.driveFileEntityService.packManyByIds(note.fileIds),
});

return packed;
}

@bindThis
private findNoteOrFail(id: string): Promise<NoteHistory> {
return this.noteHistoryRepository.findOneOrFail({
where: { id },
relations: ['user'],
});
}
}
2 changes: 2 additions & 0 deletions packages/backend/src/misc/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
} from '@/models/json-schema/meta.js';
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
import { packedNoteHistorySchema } from '@/models/json-schema/note-history.js';

export const refs = {
UserLite: packedUserLiteSchema,
Expand Down Expand Up @@ -115,6 +116,7 @@ export const refs = {
MetaDetailed: packedMetaDetailedSchema,
SystemWebhook: packedSystemWebhookSchema,
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
NoteHistory: packedNoteHistorySchema,
};

export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
Expand Down
17 changes: 16 additions & 1 deletion packages/backend/src/models/NoteHistory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne, OneToOne, OneToMany } from 'typeorm';
import { noteVisibilities } from '@/types.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
import { MiNote } from './Note.js';
Expand All @@ -18,7 +19,7 @@ export class NoteHistory {
nullable: false,
comment: 'The target Note ID for history',
})
noteId: MiNote['id'];
public noteId: MiNote['id'];

@Column('timestamp with time zone')
public updatedAt: Date;
Expand All @@ -43,4 +44,18 @@ export class NoteHistory {
length: 128, array: true, default: '{}',
})
public emojis: string[];

@Column('text', {
nullable: true,
})
public text: string | null;

@Column('enum', { enum: noteVisibilities })
public visibility: typeof noteVisibilities[number];

@Column({
...id(),
array: true, default: '{}',
})
public visibleUserIds: MiUser['id'][];
}
75 changes: 75 additions & 0 deletions packages/backend/src/models/json-schema/note-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export const packedNoteHistorySchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
noteId: {
type: 'string',
optional: false, nullable: false,
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
text: {
type: 'string',
optional: false, nullable: true,
},
fileIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
files: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
visibility: {
type: 'string',
optional: false, nullable: false,
enum: ['public', 'home', 'followers', 'specified'],
},
visibleUserIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
emojis: {
type: 'object',
optional: true, nullable: false,
additionalProperties: {
anyOf: [{
type: 'string',
}],
},
},
},
} as const;

0 comments on commit 7381d71

Please sign in to comment.