Skip to content

Commit

Permalink
feat: Notification delete (#76)
Browse files Browse the repository at this point in the history
* feat(backend): ้€š็Ÿฅใฎๅ€‹ๅˆฅๅ‰Š้™ค

* add locale

* Update CHANGELOG
  • Loading branch information
1673beta authored Jul 8, 2024
1 parent 3880cf1 commit c9d98c0
Show file tree
Hide file tree
Showing 19 changed files with 189 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_engawa.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@

### Client
- ใƒ€ใ‚คใ‚นใ‚ฆใ‚ฃใ‚ธใ‚งใƒƒใƒˆใ‚’่ฟฝๅŠ 
- ้€š็Ÿฅใ‚’ๅ…จใฆๅ‰Š้™คใงใใ‚‹ใƒœใ‚ฟใƒณใ‚’่ฟฝๅŠ 

### Server
- ็ฎก็†่€…ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ๅˆฅใ‚ตใƒผใƒใƒผใซ็งป่กŒใงใใ‚‹ใ‚ˆใ†ใซ
- APIใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’Redocใ‹ใ‚‰scalarใซใ—ใฆ่ปฝ้‡ๅŒ–
- fix: OAuthใซใƒฌใƒผใƒˆใƒชใƒŸใƒƒใƒˆใŒใ‹ใ‹ใฃใฆใ„ใชใ„ๅ•้กŒ
- fix: SQLใ‚จใ‚นใ‚ฑใƒผใƒ—ใŒไธๅฎŒๅ…จใชๅ•้กŒ
- feat: ้€š็Ÿฅใ‚’ๅ€‹ๅˆฅๅ‰Š้™คใ™ใ‚‹API

### Misc

Expand Down
1 change: 1 addition & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ channel: "Channels"
create: "Create"
notificationSetting: "Notification settings"
notificationSettingDesc: "Select the types of notification to display."
notificationFlush: "Flush ALL Notifications"
useGlobalSetting: "Use global settings"
useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
other: "Other"
Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3138,6 +3138,10 @@ export interface Locale extends ILocale {
* ่กจ็คบใ™ใ‚‹้€š็Ÿฅใฎ็จฎๅˆฅใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚
*/
"notificationSettingDesc": string;
/**
* ้€š็Ÿฅใ‚’ๅ…จ้ƒจๅ‰Š้™ค
*/
"notificationFlush": string;
/**
* ใ‚ฐใƒญใƒผใƒใƒซ่จญๅฎšใ‚’ไฝฟใ†
*/
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ channel: "ใƒใƒฃใƒณใƒใƒซ"
create: "ไฝœๆˆ"
notificationSetting: "้€š็Ÿฅ่จญๅฎš"
notificationSettingDesc: "่กจ็คบใ™ใ‚‹้€š็Ÿฅใฎ็จฎๅˆฅใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚"
notificationFlush: "้€š็Ÿฅใ‚’ๅ…จ้ƒจๅ‰Š้™ค"
useGlobalSetting: "ใ‚ฐใƒญใƒผใƒใƒซ่จญๅฎšใ‚’ไฝฟใ†"
useGlobalSettingDesc: "ใ‚ชใƒณใซใ™ใ‚‹ใจใ€ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฎ้€š็Ÿฅ่จญๅฎšใŒไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚ใ‚ชใƒ•ใซใ™ใ‚‹ใจใ€ๅ€‹ๅˆฅใซ่จญๅฎšใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚"
other: "ใใฎไป–"
Expand Down
1 change: 1 addition & 0 deletions locales/ja-KS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ channel: "ใƒใƒฃใƒณใƒใƒซ"
create: "ไฝœๆˆ"
notificationSetting: "้€š็Ÿฅ่จญๅฎš"
notificationSettingDesc: "ๅ‡บใ™้€š็Ÿฅใฎ็จฎ้กžใˆใ‚‰ใ‚“ใงใ‚„ใ€‚"
notificationFlush: "้€š็Ÿฅๅ…จ้ƒจๆถˆใ—ใŸใ‚‹"
useGlobalSetting: "ใ‚ฐใƒญใƒผใƒใƒซ่จญๅฎšใ‚’ไฝฟใฃใฆใ‚„"
useGlobalSettingDesc: "ใ‚ชใƒณใซใ™ใ‚‹ใจใ€ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฎ้€š็Ÿฅ่จญๅฎšใŒไฝฟใ‚ใ‚Œใ‚‹ใงใ€‚ใ‚ชใƒ•ใซใ™ใ‚‹ใจใ€ๅˆฅใ€…ใซ่จญๅฎšใงใใ‚‹ใ‚ˆใ†ใซใชใ‚‹ใงใ€‚"
other: "ใใฎไป–"
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/GlobalEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { MiPage } from '@/models/Page.js';
import type { MiWebhook } from '@/models/Webhook.js';
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
import type { MiMeta } from '@/models/Meta.js';
import type { MiNotification } from '@/models/Notification.js';
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
Expand Down Expand Up @@ -73,6 +74,7 @@ export interface MainEventTypes {
};
readAllNotifications: undefined;
notificationFlushed: undefined;
notificationDeleted: MiNotification['id'];
unreadNotification: Packed<'Notification'>;
unreadMention: MiNote['id'];
readAllUnreadMentions: undefined;
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/core/NotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,26 @@ export class NotificationService implements OnApplicationShutdown {
this.globalEventService.publishMainStream(userId, 'notificationFlushed');
}

async #getNotifications(userId: MiUser['id'], notificationId: MiNotification['id']) {
const notificationRes = await this.redisClient.xrange(
`notificationTimeline:${userId}`,
`${this.idService.parse(notificationId).date.getTime() - 1000}-0`,
`${this.idService.parse(notificationId).date.getTime() + 1000}-9999 `,
'COUNT', 50
);
return notificationRes.find(x => JSON.parse(x[1][1]).id === notificationId);
}

@bindThis
public async deleteNotification(userId: MiUser['id'], notificationId: MiNotification['id']) : Promise<MiNotification['id'] | void> {
const targetResId = (await this.#getNotifications(userId, notificationId))?.[0];
if (!targetResId) return;

await this.redisClient.xdel(`notificationTimeline:${userId}`, targetResId);
this.globalEventService.publishMainStream(userId, 'notificationDeleted', notificationId);
return notificationId;
}

@bindThis
public dispose(): void {
this.#shutdownController.abort();
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/server/api/EndpointsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_delete from './endpoints/notifications/delete.js';
import * as ep___notifications_flush from './endpoints/notifications/flush.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
Expand Down Expand Up @@ -737,6 +738,7 @@ const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep
const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
const $notifications_delete: Provider = { provide: 'ep:notifications/delete', useClass: ep___notifications_delete.default };
const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
Expand Down Expand Up @@ -1157,6 +1159,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_unrenote,
$notes_userListTimeline,
$notifications_create,
$notifications_delete,
$notifications_flush,
$notifications_markAllAsRead,
$notifications_testNotification,
Expand Down Expand Up @@ -1569,6 +1572,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_unrenote,
$notes_userListTimeline,
$notifications_create,
$notifications_delete,
$notifications_flush,
$notifications_markAllAsRead,
$notifications_testNotification,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_delete from './endpoints/notifications/delete.js';
import * as ep___notifications_flush from './endpoints/notifications/flush.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
Expand Down Expand Up @@ -740,6 +741,7 @@ const eps = [
['notes/unrenote', ep___notes_unrenote],
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
['notifications/delete', ep___notifications_delete],
['notifications/flush', ep___notifications_flush],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
['notifications/test-notification', ep___notifications_testNotification],
Expand Down
48 changes: 48 additions & 0 deletions packages/backend/src/server/api/endpoints/notifications/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ApiError } from '../../error.js';

export const meta = {
tags: ['notification', 'account'],

requireCredential: true,

kind: 'write:notifications',

errors: {
'noSuchNotification': {
message: 'No such notification',
code: 'NO_SUCH_NOTIFICATION',
id: '4818a20e-3d02-11ef-9c7c-63e2e6b43b02',
},
},
} as const;


export const paramDef = {
type: 'object',
properties: {
notificationId: { type: 'string', format: 'misskey:id' },
},
required: ['notificationId'],
} as const;

@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
const res = await this.notificationService.deleteNotification(me.id, ps.notificationId);
if (!res) {
throw new ApiError(meta.errors.noSuchNotification);
}
});
}
}
5 changes: 5 additions & 0 deletions packages/cherrypick-js/etc/cherrypick-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ export type Channels = {
unreadMention: (payload: Note['id']) => void;
readAllUnreadMentions: () => void;
notificationFlushed: () => void;
notificationDeleted: () => void;
unreadSpecifiedNote: (payload: Note['id']) => void;
readAllUnreadSpecifiedNotes: () => void;
readAllMessagingMessages: () => void;
Expand Down Expand Up @@ -1716,6 +1717,7 @@ declare namespace entities {
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotificationsCreateRequest,
NotificationsDeleteRequest,
PagePushRequest,
PagesCreateRequest,
PagesCreateResponse,
Expand Down Expand Up @@ -2832,6 +2834,9 @@ type Notification_2 = components['schemas']['Notification'];
// @public (undocumented)
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];

// @public (undocumented)
type NotificationsDeleteRequest = operations['notifications___delete']['requestBody']['content']['application/json'];

// @public (undocumented)
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];

Expand Down
11 changes: 11 additions & 0 deletions packages/cherrypick-js/src/autogen/apiClientJSDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3517,6 +3517,17 @@ declare module '../api.js' {
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;

/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
request<E extends 'notifications/delete', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;

/**
* No description provided.
*
Expand Down
2 changes: 2 additions & 0 deletions packages/cherrypick-js/src/autogen/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ import type {
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotificationsCreateRequest,
NotificationsDeleteRequest,
PagePushRequest,
PagesCreateRequest,
PagesCreateResponse,
Expand Down Expand Up @@ -936,6 +937,7 @@ export type Endpoints = {
'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse };
'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse };
'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
'notifications/delete': { req: NotificationsDeleteRequest; res: EmptyResponse };
'notifications/flush': { req: EmptyRequest; res: EmptyResponse };
'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse };
'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse };
Expand Down
1 change: 1 addition & 0 deletions packages/cherrypick-js/src/autogen/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ export type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']
export type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['requestBody']['content']['application/json'];
export type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json'];
export type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
export type NotificationsDeleteRequest = operations['notifications___delete']['requestBody']['content']['application/json'];
export type PagePushRequest = operations['page-push']['requestBody']['content']['application/json'];
export type PagesCreateRequest = operations['pages___create']['requestBody']['content']['application/json'];
export type PagesCreateResponse = operations['pages___create']['responses']['200']['content']['application/json'];
Expand Down
61 changes: 61 additions & 0 deletions packages/cherrypick-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3036,6 +3036,15 @@ export type paths = {
*/
post: operations['notifications___create'];
};
'/notifications/delete': {
/**
* notifications/delete
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
post: operations['notifications___delete'];
};
'/notifications/flush': {
/**
* notifications/flush
Expand Down Expand Up @@ -24393,6 +24402,58 @@ export type operations = {
};
};
};
/**
* notifications/delete
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
notifications___delete: {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
notificationId: string;
};
};
};
responses: {
/** @description OK (without any results) */
204: {
content: never;
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* notifications/flush
* @description No description provided.
Expand Down
1 change: 1 addition & 0 deletions packages/cherrypick-js/src/streaming.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type Channels = {
unreadMention: (payload: Note['id']) => void;
readAllUnreadMentions: () => void;
notificationFlushed: () => void;
notificationDeleted: () => void;
unreadSpecifiedNote: (payload: Note['id']) => void;
readAllUnreadSpecifiedNotes: () => void;
readAllMessagingMessages: () => void;
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/components/MkNotifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ let connection: Misskey.ChannelConnection<Misskey.Channels['main']>;
onMounted(() => {
connection = useStream().useChannel('main');
connection.on('notification', onNotification);
connection.on('notificationDeleted', reload);
connection.on('notificationFlushed', reload);
globalEvents.on('reloadNotification', () => reload());
Expand All @@ -92,6 +93,7 @@ onActivated(() => {
pagingComponent.value?.reload();
connection = useStream().useChannel('main');
connection.on('notification', onNotification);
connection.on('notificationDeleted', reload);
connection.on('notificationFlushed', reload);
});
Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/pages/notifications-friendly.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ const headerActions = computed(() => [deviceKind === 'desktop' && !props.disable
handler: () => {
os.apiWithDialog('notifications/mark-all-as-read');
},
} : undefined, tab.value === 'all' ? {
text: i18n.ts.notificationFlush,
icon: 'ti ti-trash',
handler: () => {
os.apiWithDialog('notifications/flush');
}
} : undefined].filter(x => x !== undefined));
const headerTabs = computed(() => [{
Expand Down
15 changes: 15 additions & 0 deletions packages/frontend/src/ui/deck/notifications-column.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,24 @@ function func() {
}, 'closed');
}
async function flushNotification() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts.resetAreYouSure,
});
if (canceled) return;
os.apiWithDialog('notifications/flush');
}
const menu = [{
icon: 'ti ti-pencil',
text: i18n.ts.notificationSetting,
action: func,
}, {
icon: 'ti ti-trash',
text: i18n.ts.notificationFlush,
action: flushNotification,
}];
</script>

0 comments on commit c9d98c0

Please sign in to comment.