Skip to content

Commit

Permalink
feat: 설정한 시간이 지나면 노트를 자동으로 삭제할 수 있음 (1673beta#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
noridev committed Oct 3, 2024
1 parent 17ed323 commit e7abb27
Show file tree
Hide file tree
Showing 25 changed files with 425 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG_CHERRYPICK.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE
- 갤러리를 QR 코드로 공유 (1673beta/cherrypick#51)
- 페이지를 QR 코드로 공유 (1673beta/cherrypick#53)
- Play를 QR 코드로 공유
- Feat: 설정한 시간이 지나면 노트를 자동으로 삭제할 수 있음 (1673beta/cherrypick#70)

### Client
- Enhance: CherryPick 업데이트 페이지를 제어판 목록에 추가함
Expand Down
8 changes: 8 additions & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
_lang_: "English"
scheduledNoteDelete: "Schedule note deletion"
getQRCode: "Get QR code"
customSplashText: "Custom splash text"
customSplashTextDescription: "This text will be displayed on the loading page."
Expand Down Expand Up @@ -3029,3 +3030,10 @@ _dice:
rollDice: "Roll the dice"
diceCount: "Number of dice"
diceFaces: "Number of dice faces"
_scheduledNoteDelete:
expiration: "End of note deletion"
at: "End at..."
after: "End after..."
deadlineDate: "End date"
deadlineTime: "Time"
duration: "Duration"
30 changes: 30 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface Locale extends ILocale {
* 日本語
*/
"_lang_": string;
/**
* ノートの削除を予約
*/
"scheduledNoteDelete": string;
/**
* QRコードを取得
*/
Expand Down Expand Up @@ -11842,6 +11846,32 @@ export interface Locale extends ILocale {
*/
"diceFaces": string;
};
"_scheduledNoteDelete": {
/**
* 期限
*/
"expiration": string;
/**
* 日時指定
*/
"at": string;
/**
* 経過指定
*/
"after": string;
/**
* 期日
*/
"deadlineDate": string;
/**
* 時間
*/
"deadlineTime": string;
/**
* 期間
*/
"duration": string;
};
}
declare const locales: {
[lang: string]: Locale;
Expand Down
9 changes: 9 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
_lang_: "日本語"

scheduledNoteDelete: "ノートの削除を予約"
getQRCode: "QRコードを取得"
customSplashText: "カスタムスプラッシュテキスト"
customSplashTextDescription: "ロード画面に表示されるテキストを設定します。改行で区切って複数設定できます。"
Expand Down Expand Up @@ -3155,3 +3156,11 @@ _dice:
rollDice: "サイコロを振る"
diceCount: "サイコロの数"
diceFaces: "サイコロの面数"

_scheduledNoteDelete:
expiration: "期限"
at: "日時指定"
after: "経過指定"
deadlineDate: "期日"
deadlineTime: "時間"
duration: "期間"
8 changes: 8 additions & 0 deletions locales/ko-KR.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
_lang_: "한국어"
scheduledNoteDelete: "노트 삭제 예약"
getQRCode: "QR 코드 생성"
customSplashText: "사용자 정의 스플래시 텍스트"
customSplashTextDescription: "스플래시 화면에 표시되는 텍스트를 설정해요. 줄바꿈으로 구분해 설정할 수 있어요."
Expand Down Expand Up @@ -3060,3 +3061,10 @@ _dice:
rollDice: "주사위 던지기"
diceCount: "주사위 개수"
diceFaces: "주사위 면의 수"
_scheduledNoteDelete:
expiration: "삭제 기한"
at: "일시 지정"
after: "기간 지정"
deadlineDate: "기한"
deadlineTime: "시간"
duration: "기간"
11 changes: 11 additions & 0 deletions packages/backend/migration/1720161864577-AddDeleteAt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class AddDeleteAt1720161864577 {
name = 'AddDeleteAt1720161864577'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ADD "deleteAt" TIMESTAMP WITH TIME ZONE`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "deleteAt"`)
}
}
12 changes: 12 additions & 0 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ type Option = {
uri?: string | null;
url?: string | null;
app?: MiApp | null;
deleteAt?: Date | null;
};

@Injectable()
Expand Down Expand Up @@ -445,6 +446,7 @@ export class NoteCreateService implements OnApplicationShutdown {
renoteUserId: data.renote ? data.renote.userId : null,
renoteUserHost: data.renote ? data.renote.userHost : null,
userHost: user.host,
deleteAt: data.deleteAt,
});

if (data.uri != null) insert.uri = data.uri;
Expand Down Expand Up @@ -745,6 +747,16 @@ export class NoteCreateService implements OnApplicationShutdown {
});
}

if (data.deleteAt) {
const delay = data.deleteAt.getTime() - Date.now();
this.queueService.scheduledNoteDeleteQueue.add(note.id, {
noteId: note.id,
}, {
delay,
removeOnComplete: true,
});
}

// Register to search database
this.index(note);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/backend/src/core/QueueModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
RelationshipJobData,
UserWebhookDeliverJobData,
SystemWebhookDeliverJobData,
ScheduledNoteDeleteJobData,
} from '../queue/types.js';
import type { Provider } from '@nestjs/common';

Expand All @@ -29,6 +30,7 @@ export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
export type ObjectStorageQueue = Bull.Queue;
export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
export type ScheduledNoteDeleteQueue = Bull.Queue<ScheduledNoteDeleteJobData>;

const $system: Provider = {
provide: 'queue:system',
Expand Down Expand Up @@ -84,6 +86,12 @@ const $systemWebhookDeliver: Provider = {
inject: [DI.config, DI.redisForJobQueue],
};

const $scheduledNoteDelete: Provider = {
provide: 'queue:scheduledNoteDelete',
useFactory: (config: Config, redisForJobQueue: Redis.Redis) => new Bull.Queue(QUEUE.SCHEDULED_NOTE_DELETE, baseQueueOptions(config, QUEUE.SCHEDULED_NOTE_DELETE, redisForJobQueue)),
inject: [DI.config, DI.redisForJobQueue],
};

@Module({
imports: [
],
Expand All @@ -97,6 +105,7 @@ const $systemWebhookDeliver: Provider = {
$objectStorage,
$userWebhookDeliver,
$systemWebhookDeliver,
$scheduledNoteDelete,
],
exports: [
$system,
Expand All @@ -108,6 +117,7 @@ const $systemWebhookDeliver: Provider = {
$objectStorage,
$userWebhookDeliver,
$systemWebhookDeliver,
$scheduledNoteDelete,
],
})
export class QueueModule implements OnApplicationShutdown {
Expand All @@ -121,6 +131,7 @@ export class QueueModule implements OnApplicationShutdown {
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduledNoteDelete') public scheduledNoteDeleteQueue: ScheduledNoteDeleteQueue,
) {}

public async dispose(): Promise<void> {
Expand All @@ -137,6 +148,7 @@ export class QueueModule implements OnApplicationShutdown {
this.objectStorageQueue.close(),
this.userWebhookDeliverQueue.close(),
this.systemWebhookDeliverQueue.close(),
this.scheduledNoteDeleteQueue.close(),
]);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/QueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
SystemQueue,
UserWebhookDeliverQueue,
SystemWebhookDeliverQueue,
ScheduledNoteDeleteQueue,
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
Expand All @@ -52,6 +53,7 @@ export class QueueService {
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@Inject('queue:scheduledNoteDelete') public scheduledNoteDeleteQueue: ScheduledNoteDeleteQueue,
) {
this.systemQueue.add('tickCharts', {
}, {
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/entities/NoteEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ export class NoteEntityService implements OnModuleInit {

poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
event: note.hasEvent ? this.populateEvent(note) : undefined,
deleteAt: note.deleteAt?.toISOString() ?? undefined,

...(meId && Object.keys(reactions).length > 0 ? {
myReaction: this.populateMyReaction({
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ export class MiNote {
comment: '[Denormalized]',
})
public renoteUserHost: string | null;

@Column('timestamp with time zone', {
nullable: true,
})
public deleteAt: Date | null;
//#endregion

constructor(data: Partial<MiNote>) {
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/src/models/json-schema/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,14 @@ export const packedNoteSchema = {
type: 'number',
optional: true, nullable: false,
},

myReaction: {
type: 'string',
optional: true, nullable: true,
},
deleteAt: {
type: 'string',
optional: true, nullable: true,
format: 'date-time',
},
},
} as const;
2 changes: 2 additions & 0 deletions packages/backend/src/queue/QueueProcessorModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
import { ScheduledNoteDeleteProcessorService } from './processors/ScheduledNoteDeleteProcessorService.js';

@Module({
imports: [
Expand Down Expand Up @@ -83,6 +84,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
InboxProcessorService,
AggregateRetentionProcessorService,
QueueProcessorService,
ScheduledNoteDeleteProcessorService,
],
exports: [
QueueProcessorService,
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/queue/QueueProcessorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { ScheduledNoteDeleteProcessorService } from './processors/ScheduledNoteDeleteProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QUEUE, baseQueueOptions } from './const.js';

Expand Down Expand Up @@ -85,6 +86,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private relationshipQueueWorker: Bull.Worker;
private objectStorageQueueWorker: Bull.Worker;
private endedPollNotificationQueueWorker: Bull.Worker;
private scheduledNoteDeleteQueueWorker: Bull.Worker;

constructor(
@Inject(DI.config)
Expand Down Expand Up @@ -127,6 +129,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
private cleanProcessorService: CleanProcessorService,
private scheduledNoteDeleteProcessorService: ScheduledNoteDeleteProcessorService,
) {
this.logger = this.queueLoggerService.logger;

Expand Down Expand Up @@ -511,6 +514,21 @@ export class QueueProcessorService implements OnApplicationShutdown {
});
}
//#endregion

//#region scheduled note delete
{
this.scheduledNoteDeleteQueueWorker = new Bull.Worker(QUEUE.SCHEDULED_NOTE_DELETE, (job) => {
if (this.config.sentryForBackend) {
return Sentry.startSpan({ name: 'Queue: ScheduledNoteDelete' }, () => this.scheduledNoteDeleteProcessorService.process(job));
} else {
return this.scheduledNoteDeleteProcessorService.process(job);
}
}, {
...baseQueueOptions(this.config, QUEUE.SCHEDULED_NOTE_DELETE, this.redisForJobQueue),
autorun: false,
});
}
//#endregion
}

@bindThis
Expand All @@ -525,6 +543,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.relationshipQueueWorker.run(),
this.objectStorageQueueWorker.run(),
this.endedPollNotificationQueueWorker.run(),
this.scheduledNoteDeleteQueueWorker.run(),
]);
}

Expand All @@ -540,6 +559,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.relationshipQueueWorker.close(),
this.objectStorageQueueWorker.close(),
this.endedPollNotificationQueueWorker.close(),
this.scheduledNoteDeleteQueueWorker.close(),
]);
}

Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/queue/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const QUEUE = {
OBJECT_STORAGE: 'objectStorage',
USER_WEBHOOK_DELIVER: 'userWebhookDeliver',
SYSTEM_WEBHOOK_DELIVER: 'systemWebhookDeliver',
SCHEDULED_NOTE_DELETE: 'scheduledNoteDelete',
};

export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE], redisConnection: Redis.Redis): Bull.QueueOptions {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { ScheduledNoteDeleteJobData } from '../types.js';

@Injectable()
export class ScheduledNoteDeleteProcessorService {
private logger: Logger;

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

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

private noteDeleteService: NoteDeleteService,
private queueLoggerService: QueueLoggerService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('scheduled-note-delete');
}

@bindThis
public async process(job: Bull.Job<ScheduledNoteDeleteJobData>): Promise<void> {
const note = await this.notesRepository.findOneBy({ id: job.data.noteId });

if (note == null) {
return;
}

const user = await this.usersRepository.findOneBy({ id: note.userId });

if (user == null) {
return;
}

await this.noteDeleteService.delete(user, note);
this.logger.info(`Delete note ${note.id}`);
}
}
Loading

0 comments on commit e7abb27

Please sign in to comment.