Skip to content

Commit

Permalink
enhance(frontend/backend): 예약된 노트 게시에 실패할 경우 사용자에게 알림 ([penginn-net/k…
Browse files Browse the repository at this point in the history
  • Loading branch information
noridev committed Dec 3, 2024
1 parent d4ab680 commit d415d09
Show file tree
Hide file tree
Showing 16 changed files with 126 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG_CHERRYPICK.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024110](CHANG
- 기간은 최소 `1`일부터 최대 `30`일까지 설정할 수 있습니다.
- `0`일로 설정하면 최소 기간인 `1`일로 설정되며, `30`일을 초과하는 값을 입력하면 최대 기간인 `30`일로 설정됩니다.
- 여기서 설정된 기간은 `초대제로 전환` 옵션과 `공개 노트 허용` 옵션 모두에 적용됩니다.
- Enhance: 예약된 노트 게시에 실패할 경우 사용자에게 알림 ([penginn-net/kokonect@a0e47980](https://github.com/penginn-net/kokonect/commit/a0e47980470b49e79e84ff3b7ccaf2b4502928c8))

### Client
- Enhance: 미디어 그리드 레이아옷 조정
Expand Down
7 changes: 7 additions & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2820,12 +2820,19 @@ _notification:
achievementEarned: "Achievement unlocked"
exportCompleted: "The export has been completed"
login: "Sign In"
scheduleNote: "Scheduled note posting failed"
test: "Notification test"
app: "Notifications from linked apps"
_actions:
followBack: "followed you back"
reply: "Reply"
renote: "Renote"
_scheduleNote:
unknown: "The cause is unknown"
renoteTargetNotFound: "Renote target note not found"
channelTargetNotFound: "Channel not found"
replyTargetNotFound: "Reply target note not found"
invalidFilesCount: "No attachments"
_deck:
alwaysShowMainColumn: "Always show main column"
columnAlign: "Align columns"
Expand Down
26 changes: 26 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10989,6 +10989,10 @@ export interface Locale extends ILocale {
* ログイン
*/
"login": string;
/**
* 予約投稿に失敗
*/
"scheduleNote": string;
/**
* 通知のテスト
*/
Expand All @@ -11012,6 +11016,28 @@ export interface Locale extends ILocale {
*/
"renote": string;
};
"_scheduleNote": {
/**
* 原因は不明です
*/
"unknown": string;
/**
* 引用元がありません
*/
"renoteTargetNotFound": string;
/**
* 対象のチャンネルがありません
*/
"channelTargetNotFound": string;
/**
* 返信先がありません
*/
"replyTargetNotFound": string;
/**
* 添付ファイルがありません
*/
"invalidFilesCount": string;
};
};
"_deck": {
/**
Expand Down
8 changes: 8 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2895,6 +2895,7 @@ _notification:
achievementEarned: "実績の獲得"
exportCompleted: "エクスポートが完了した"
login: "ログイン"
scheduleNote: "予約投稿に失敗"
test: "通知のテスト"
app: "連携アプリからの通知"

Expand All @@ -2903,6 +2904,13 @@ _notification:
reply: "返信"
renote: "リノート"

_scheduleNote:
unknown: "原因は不明です"
renoteTargetNotFound: "引用元がありません"
channelTargetNotFound: "対象のチャンネルがありません"
replyTargetNotFound: "返信先がありません"
invalidFilesCount: "添付ファイルがありません"

_deck:
alwaysShowMainColumn: "常にメインカラムを表示"
columnAlign: "カラムの寄せ"
Expand Down
7 changes: 7 additions & 0 deletions locales/ko-KR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2823,12 +2823,19 @@ _notification:
achievementEarned: "도전 과제 획득"
exportCompleted: "내보내기를 완료함"
login: "로그인"
scheduleNote: "게시가 예약된 노트의 게시가 실패함"
test: "알림 테스트"
app: "연동된 앱을 통한 알림"
_actions:
followBack: "팔로우"
reply: "답글"
renote: "리노트"
_scheduleNote:
unknown: "알 수 없는 오류가 발생했어요"
renoteTargetNotFound: "인용할 대상이 없어요"
channelTargetNotFound: "해당 채널이 존재하지 않아요"
replyTargetNotFound: "답장할 대상이 없어요"
invalidFilesCount: "첨부 파일이 없어요"
_deck:
alwaysShowMainColumn: "메인 칼럼 항상 표시"
columnAlign: "칼럼 정렬"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'login' ? {
ip: notification.userIp,
} : {}),
...(notification.type === 'scheduleNote' ? {
errorType: notification.errorType,
} : {}),
...(notification.type === 'app' ? {
body: notification.customBody,
header: notification.customHeader,
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export type MiNotification = {
id: string;
createdAt: string;
userIp: string;
} | {
type: 'scheduleNote';
id: string;
createdAt: string;
errorType: string;
} | {
type: 'app';
id: string;
Expand Down
14 changes: 14 additions & 0 deletions packages/backend/src/models/json-schema/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ export const packedNotificationSchema = {
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['scheduleNote'],
},
errorType: {
type: 'string',
optional: false, nullable: false,
},
},
}, {
type: 'object',
properties: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { bindThis } from '@/decorators.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import type { ChannelsRepository, DriveFilesRepository, MiDriveFile, NoteScheduleRepository, NotesRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { NotificationService } from '@/core/NotificationService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { ScheduleNotePostJobData } from '../types.js';
Expand All @@ -32,6 +33,7 @@ export class ScheduleNotePostProcessorService {

private noteCreateService: NoteCreateService,
private queueLoggerService: QueueLoggerService,
private notificationService: NotificationService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post');
}
Expand Down Expand Up @@ -72,6 +74,25 @@ export class ScheduleNotePostProcessorService {
//キューに積んだときは有った物が消滅してたら予約投稿をキャンセルする
this.logger.warn('cancel schedule note');
await this.noteScheduleRepository.remove(data);

if (data.userId && me) { //ユーザーが特定できる場合に失敗を通知
let errorType = 'unknown';
if (note.renote && !renote) {
errorType = 'renoteTargetNotFound';
}
if (note.reply && !reply) {
errorType = 'replyTargetNotFound';
}
if (note.channel && !channel) {
errorType = 'channelTargetNotFound';
}
if (note.files.length !== files.length) {
errorType = 'invalidFilesCount';
}
this.notificationService.createNotification(data.userId, 'scheduleNote', {
errorType,
});
}
return;
}
await this.noteCreateService.create(me, {
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
'scheduleNote',
'app',
'test',
] as const;
Expand Down
2 changes: 1 addition & 1 deletion packages/cherrypick-js/etc/cherrypick-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3004,7 +3004,7 @@ type Notification_2 = components['schemas']['Notification'];
type NotificationsCreateRequest = operations['notifications___create']['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"];
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "scheduleNote"];

// @public (undocumented)
export function nyaize(text: string): string;
Expand Down
16 changes: 12 additions & 4 deletions packages/cherrypick-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4715,6 +4715,14 @@ export type components = {
/** @enum {string} */
type: 'login';
ip: string;
} | {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
type: 'scheduleNote';
errorType: string;
} | ({
/** Format: id */
id: string;
Expand Down Expand Up @@ -19667,8 +19675,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'pollVote')[];
};
};
};
Expand Down Expand Up @@ -19735,8 +19743,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduleNote' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote')[];
};
};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/cherrypick-js/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
UserLite,
} from './autogen/models.js';

export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const;
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'scheduleNote'] as const;

export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;

Expand Down
1 change: 1 addition & 0 deletions packages/frontend-shared/js/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
'scheduleNote',
'test',
'app',
] as const;
Expand Down
13 changes: 12 additions & 1 deletion packages/frontend/src/components/MkNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'note:grouped'" :class="[$style.icon, $style.icon_noteGroup]"><i class="ti ti-pencil" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'scheduleNote'" :class="[$style.icon, $style.icon_scheduleNote]"><i class="ti ti-alert-triangle" style="line-height: 1;"></i></div>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
Expand Down Expand Up @@ -64,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
<span v-else-if="notification.type === 'scheduleNote'">{{ i18n.ts._notification._types.scheduleNote }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
Expand Down Expand Up @@ -141,6 +143,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectGroupInvitation()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
</div>
</template>
<span v-else-if="notification.type === 'scheduleNote'" :class="$style.text">{{ i18n.ts._notification._scheduleNote[notification.errorType] }}</span>
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
<span v-else-if="notification.type === 'app'" :class="$style.text">
<Mfm :text="notification.body" :nowrap="false"/>
Expand Down Expand Up @@ -285,7 +288,8 @@ const rejectGroupInvitation = () => {
.icon_reactionGroup,
.icon_reactionGroupHeart,
.icon_renoteGroup,
.icon_noteGroup {
.icon_noteGroup,
.icon_scheduleNote {
display: grid;
align-items: center;
justify-items: center;
Expand All @@ -312,6 +316,13 @@ const rejectGroupInvitation = () => {
background: var(--eventRenote);
}

.icon_scheduleNote {
width: 100%;
height: 100%;
color: var(--warn);
background: var(--eventOther);
}

.icon_app {
border-radius: 6px;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/sw/src/scripts/create-notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
data,
}];

case 'scheduleNote':
return [i18n.ts._notification._types.scheduleNote, {
body: data.body.errorType,
data,
}];

case 'app':
return [data.body.header ?? data.body.body, {
body: data.body.header ? data.body.body : '',
Expand Down

0 comments on commit d415d09

Please sign in to comment.