From 83c971b1a8918615a12921924acc3bdc6eb2aa89 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 9 Mar 2024 14:48:29 -0800 Subject: [PATCH 1/5] Add noteAutoDelete to user_profile --- .../migration/1709530777533-autoDeleteNotes.js | 14 ++++++++++++++ .../backend/src/core/entities/UserEntityService.ts | 2 ++ packages/backend/src/models/UserProfile.ts | 11 +++++++++++ packages/backend/src/models/json-schema/user.ts | 8 ++++++++ .../src/server/api/endpoints/admin/show-user.ts | 10 ++++++++++ .../backend/src/server/api/endpoints/i/update.ts | 4 ++++ 6 files changed, 49 insertions(+) create mode 100644 packages/backend/migration/1709530777533-autoDeleteNotes.js diff --git a/packages/backend/migration/1709530777533-autoDeleteNotes.js b/packages/backend/migration/1709530777533-autoDeleteNotes.js new file mode 100644 index 0000000000..2b296c4f29 --- /dev/null +++ b/packages/backend/migration/1709530777533-autoDeleteNotes.js @@ -0,0 +1,14 @@ +export class AutoDeleteNotes1709530777533 { + name = "AutoDeleteNotes1709530777533"; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE user_profile ADD "autoDeleteNotes" boolean NOT NULL DEFAULT false;`); + await queryRunner.query(`ALTER TABLE user_profile ADD "autoDeleteNotesMinutes" integer NOT NULL DEFAULT 43200;`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE user_profile DROP COLUMN "autoDeleteNotes";`); + await queryRunner.query(`ALTER TABLE user_profile DROP COLUMN "autoDeleteNotesMinutes";`); + } + +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 8f5d986fac..20a3ca64c7 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -509,6 +509,8 @@ export class UserEntityService implements OnModuleInit { mutedWords: profile!.mutedWords, hardMutedWords: profile!.hardMutedWords, mutedInstances: profile!.mutedInstances, + autoDeleteNotes: profile!.autoDeleteNotes, + autoDeleteNotesMinutes: profile!.autoDeleteNotesMinutes, mutingNotificationTypes: [], // 後方互換性のため notificationRecieveConfig: profile!.notificationRecieveConfig, emailNotificationTypes: profile!.emailNotificationTypes, diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 40ea26f610..f0e06866a4 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -277,6 +277,17 @@ export class MiUserProfile { unlockedAt: number; }[]; + @Column('boolean', { + default: false, + }) + public autoDeleteNotes: boolean; + + @Column('integer', { + default: 43200, // 30 days in minutes + }) + public autoDeleteNotesMinutes: number; + + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 33a3efd453..c31291a6a4 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -605,6 +605,14 @@ export const packedMeDetailedOnlySchema = { nullable: false, optional: false, }, }, + autoDeleteNotes: { + type: 'boolean', + nullable: false, optional: false, + }, + autoDeleteNotesMinutes: { + type: 'number', + nullable: false, optional: false, + }, notificationRecieveConfig: { type: 'object', nullable: false, optional: false, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 9cdca02224..818322eb8e 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -88,6 +88,14 @@ export const meta = { type: 'string', }, }, + autoDeleteNotes: { + type: 'boolean', + optional: false, nullable: false, + }, + autoDeleteNotesMinutes: { + type: 'number', + optional: false, nullable: false, + }, notificationRecieveConfig: { type: 'object', optional: false, nullable: false, @@ -239,6 +247,8 @@ export default class extends Endpoint { // eslint- receiveAnnouncementEmail: profile.receiveAnnouncementEmail, mutedWords: profile.mutedWords, mutedInstances: profile.mutedInstances, + autoDeleteNotesMinutes: profile.autoDeleteNotesMinutes, + autoDeleteNotes: profile.autoDeleteNotes, notificationRecieveConfig: profile.notificationRecieveConfig, isModerator: isModerator, isSilenced: isSilenced, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index cb413de9ee..7bd3cbf68d 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -202,6 +202,8 @@ export const paramDef = { mutedInstances: { type: 'array', items: { type: 'string', } }, + autoDeleteNotes: { type: 'boolean', nullable: false }, + autoDeleteNotesMinutes: { type: 'number', nullable: false, minimum: 1 }, notificationRecieveConfig: { type: 'object', nullable: false, @@ -319,6 +321,8 @@ export default class extends Endpoint { // eslint- profileUpdates.hardMutedWords = ps.hardMutedWords; } if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances; + if (ps.autoDeleteNotes !== undefined) profileUpdates.autoDeleteNotes = ps.autoDeleteNotes; + if (ps.autoDeleteNotesMinutes !== undefined) profileUpdates.autoDeleteNotesMinutes = ps.autoDeleteNotesMinutes; if (ps.notificationRecieveConfig !== undefined) profileUpdates.notificationRecieveConfig = ps.notificationRecieveConfig; if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; -- 2.34.1 From d2db36d0ee2244366154425d817bcf6ed370c7f5 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 9 Mar 2024 14:49:56 -0800 Subject: [PATCH 2/5] Add month and years to locale file --- locales/en-US.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locales/en-US.yml b/locales/en-US.yml index c489ec4d79..dfe066af99 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -947,6 +947,7 @@ oneHour: "One hour" oneDay: "One day" oneWeek: "One week" oneMonth: "One month" +oneYear: "One year" reflectMayTakeTime: "It may take some time for this to be reflected." failedToFetchAccountInformation: "Could not fetch account information" rateLimitExceeded: "Rate limit exceeded" @@ -2000,6 +2001,8 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" + month: "Month(s)" + year: "Year(s)" _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" -- 2.34.1 From fc1727870cc5731a13f01d776cb63fc1b5be338f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 9 Mar 2024 14:50:17 -0800 Subject: [PATCH 3/5] Add noteAutoDelete form to privacy page --- locales/en-US.yml | 1 + .../src/pages/settings/privacy.autodelete.vue | 66 +++++++++++++++++++ .../frontend/src/pages/settings/privacy.vue | 10 +++ 3 files changed, 77 insertions(+) create mode 100644 packages/frontend/src/pages/settings/privacy.autodelete.vue diff --git a/locales/en-US.yml b/locales/en-US.yml index dfe066af99..1583286ada 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -99,6 +99,7 @@ serverIsDead: "This server is not responding. Please wait for a while and try ag youShouldUpgradeClient: "To view this page, please refresh to update your client." enterListName: "Enter a name for the list" privacy: "Privacy" +autoDeleteNotes: "Self-destructing notes" makeFollowManuallyApprove: "Follow requests require approval" defaultNoteVisibility: "Default visibility" follow: "Follow" diff --git a/packages/frontend/src/pages/settings/privacy.autodelete.vue b/packages/frontend/src/pages/settings/privacy.autodelete.vue new file mode 100644 index 0000000000..cec6da21d0 --- /dev/null +++ b/packages/frontend/src/pages/settings/privacy.autodelete.vue @@ -0,0 +1,66 @@ + + + diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index 86cf5ab241..36f3319871 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -68,6 +68,15 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.keepCw }} + + +
+ + + + +
+
@@ -82,6 +91,7 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { signinRequired } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import XAutoDelete from './privacy.autodelete.vue'; const $i = signinRequired(); -- 2.34.1 From d60344dfac20d65c0868735dba72e136a235ab96 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 12 Mar 2024 17:12:30 -0700 Subject: [PATCH 4/5] Add AutoDeleteNotesProcessorService --- packages/backend/src/core/QueueService.ts | 6 ++ .../backend/src/queue/QueueProcessorModule.ts | 2 + .../src/queue/QueueProcessorService.ts | 3 + .../AutoDeleteNotesProcessorService.ts | 77 +++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 packages/backend/src/queue/processors/AutoDeleteNotesProcessorService.ts diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 103813acf2..96b52c33b4 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -69,6 +69,12 @@ export class QueueService { repeat: { pattern: '*/5 * * * *' }, removeOnComplete: true, }); + + this.systemQueue.add('autoDeleteNotes', { + }, { + repeat: { pattern: '*/5 * * * *' }, + removeOnComplete: true, + }); } @bindThis diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index d7316e19e3..593e1d4b2f 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -13,6 +13,7 @@ import { EndedPollNotificationProcessorService } from './processors/EndedPollNot import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js'; import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; +import { AutoDeleteNotesProcessorService } from './processors/AutoDeleteNotesProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; @@ -52,6 +53,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor ResyncChartsProcessorService, CleanChartsProcessorService, CheckExpiredMutingsProcessorService, + AutoDeleteNotesProcessorService, CleanProcessorService, DeleteDriveFilesProcessorService, ExportAccountDataProcessorService, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 76b6d7fb05..d8f39dd5c4 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -38,6 +38,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; +import { AutoDeleteNotesProcessorService } from './processors/AutoDeleteNotesProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; @@ -118,6 +119,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private cleanChartsProcessorService: CleanChartsProcessorService, private aggregateRetentionProcessorService: AggregateRetentionProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, + private autoDeleteNotesProcessorService: AutoDeleteNotesProcessorService, private cleanProcessorService: CleanProcessorService, ) { this.logger = this.queueLoggerService.logger; @@ -146,6 +148,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'cleanCharts': return this.cleanChartsProcessorService.process(); case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); + case 'autoDeleteNotes': return this.autoDeleteNotesProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); default: throw new Error(`unrecognized job type ${job.name} for system`); } diff --git a/packages/backend/src/queue/processors/AutoDeleteNotesProcessorService.ts b/packages/backend/src/queue/processors/AutoDeleteNotesProcessorService.ts new file mode 100644 index 0000000000..c3a057c3b2 --- /dev/null +++ b/packages/backend/src/queue/processors/AutoDeleteNotesProcessorService.ts @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { UserProfilesRepository, NotesRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { NoteDeleteService } from '@/core/NoteDeleteService.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; + +@Injectable() +export class AutoDeleteNotesProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + @Inject(DI.userNotePiningsRepository) + private userNotePiningsRepository: UserNotePiningsRepository, + + private idService: IdService, + private noteDeleteService: NoteDeleteService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('auto-delete-notes'); + } + + @bindThis + public async process(): Promise { + this.logger.info('Auto deleting old notes...'); + + const userProfiles = await this.userProfilesRepository.createQueryBuilder('user_profile') + .innerJoinAndSelect('user_profile.user', 'user') + .where('user.host IS NULL') + .andWhere('user_profile.autoDeleteNotes') + .getMany(); + + for (const userProfile of userProfiles) { + const user = userProfile.user; + this.logger.debug(`Deleting old notes of user @${user.username} (id ${user.id})`); + const untilTime = Date.now() - (userProfile.autoDeleteNotesMinutes * 1000 * 60); + const untilId = this.idService.gen(untilTime); + + const pins = await this.userNotePiningsRepository.createQueryBuilder('user_note_pining') + .where('"userId" = :userId', { userId: user.id }) + .getMany(); + + const pinnedNoteIds = pins.map((p) => p.noteId); + + const notes = await this.notesRepository.createQueryBuilder('note') + .where('note."userId" = :userId', { userId: user.id }) + .andWhere('note.id < :untilId', { untilId }) + .andWhere('note.id NOT IN (SELECT "noteId" FROM note_favorite WHERE "userId" = :userId)') + .getMany(); + + for (const note of notes) { + if (pinnedNoteIds.includes(note.id)) { + this.logger.debug(`Skipping note ${note.id} as it is pinned`); + continue; + } + + this.logger.debug(`Deleting note ${note.id}`); + await this.noteDeleteService.delete(user, note, false, user); + } + } + + this.logger.succ('Done with note auto-delete'); + } +} -- 2.34.1 From cfff11f0c0e2764a59ee98267b97fbb37f975f0b Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sun, 17 Mar 2024 21:42:39 -0700 Subject: [PATCH 5/5] Improve description of autodelete feature --- .../frontend/src/pages/settings/privacy.autodelete.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/settings/privacy.autodelete.vue b/packages/frontend/src/pages/settings/privacy.autodelete.vue index cec6da21d0..34675fb5c3 100644 --- a/packages/frontend/src/pages/settings/privacy.autodelete.vue +++ b/packages/frontend/src/pages/settings/privacy.autodelete.vue @@ -3,7 +3,14 @@ {{ i18n.ts.save }} {{ i18n.ts.enable }} - + -- 2.34.1