feat: お知らせの確認待機時間・優先順位機能

b7fd6bf33a835fd73c2a86eb007074d3680f6efd によるリワーク

This reverts commit eeef3965b7
This reverts commit 04fefb2056
This reverts commit 576251200f
This commit is contained in:
まっちゃとーにゅ
2023-08-19 04:48:28 +09:00
parent 3c2b83c3ae
commit 6893e5d60b
27 changed files with 832 additions and 264 deletions

View File

@@ -45,6 +45,34 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
icon: {
type: 'string',
optional: false, nullable: false,
},
display: {
type: 'string',
optional: false, nullable: false,
},
forExistingUsers: {
type: 'boolean',
optional: false, nullable: false,
},
needConfirmationToRead: {
type: 'boolean',
optional: false, nullable: false,
},
closeDuration: {
type: 'number',
optional: false, nullable: false,
},
displayOrder: {
type: 'number',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: true,
},
},
},
} as const;
@@ -59,6 +87,8 @@ export const paramDef = {
display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' },
forExistingUsers: { type: 'boolean', default: false },
needConfirmationToRead: { type: 'boolean', default: false },
closeDuration: { type: 'number', default: 0 },
displayOrder: { type: 'number', default: 0 },
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
},
required: ['title', 'text', 'imageUrl'],
@@ -81,10 +111,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
display: ps.display,
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
closeDuration: ps.closeDuration,
displayOrder: ps.displayOrder,
userId: ps.userId,
});
return packed;
return {
id: packed.id,
createdAt: packed.createdAt,
updatedAt: packed.updatedAt,
title: packed.title,
text: packed.text,
imageUrl: packed.imageUrl,
icon: packed.icon,
display: packed.display,
forExistingUsers: raw.forExistingUsers,
needConfirmationToRead: packed.needConfirmationToRead,
closeDuration: packed.closeDuration,
displayOrder: packed.displayOrder,
userId: raw.userId,
};
});
}
}

View File

@@ -5,9 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
import type { AnnouncementsRepository } from '@/models/index.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
export const meta = {
tags: ['admin'],
@@ -38,13 +39,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.delete(announcement.id);
await this.announcementService.delete(announcement.id);
});
}
}

View File

@@ -3,12 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js';
import type { Announcement } from '@/models/entities/Announcement.js';
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
export const meta = {
tags: ['admin'],
@@ -39,19 +36,56 @@ export const meta = {
optional: false, nullable: true,
format: 'date-time',
},
text: {
type: 'string',
isActive: {
type: 'boolean',
optional: false, nullable: false,
},
title: {
type: 'string',
optional: false, nullable: false,
},
text: {
type: 'string',
optional: false, nullable: false,
},
imageUrl: {
type: 'string',
optional: false, nullable: true,
},
reads: {
icon: {
type: 'string',
optional: false, nullable: false,
},
display: {
type: 'string',
optional: false, nullable: false,
},
forExistingUsers: {
type: 'boolean',
optional: false, nullable: false,
},
needConfirmationToRead: {
type: 'boolean',
optional: false, nullable: false,
},
closeDuration: {
type: 'number',
optional: false, nullable: false,
},
displayOrder: {
type: 'number',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: true,
},
user: {
type: 'object',
optional: false, nullable: true,
ref: 'UserLite',
},
readCount: {
type: 'number',
optional: false, nullable: false,
},
@@ -64,8 +98,7 @@ export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
offset: { type: 'integer', default: 0 },
userId: { type: 'string', format: 'misskey:id', nullable: true },
},
required: [],
@@ -75,46 +108,28 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
@Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository,
private queryService: QueryService,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
if (ps.userId) {
query.andWhere('announcement.userId = :userId', { userId: ps.userId });
} else {
query.andWhere('announcement.userId IS NULL');
}
const announcements = await query.limit(ps.limit).getMany();
const reads = new Map<Announcement, number>();
for (const announcement of announcements) {
reads.set(announcement, await this.announcementReadsRepository.countBy({
announcementId: announcement.id,
}));
}
const announcements = await this.announcementService.list(ps.userId ?? null, ps.limit, ps.offset, me);
return announcements.map(announcement => ({
id: announcement.id,
createdAt: announcement.createdAt.toISOString(),
updatedAt: announcement.updatedAt?.toISOString() ?? null,
isActive: announcement.isActive,
title: announcement.title,
text: announcement.text,
imageUrl: announcement.imageUrl,
icon: announcement.icon,
display: announcement.display,
isActive: announcement.isActive,
forExistingUsers: announcement.forExistingUsers,
needConfirmationToRead: announcement.needConfirmationToRead,
closeDuration: announcement.closeDuration,
displayOrder: announcement.displayOrder,
userId: announcement.userId,
reads: reads.get(announcement)!,
user: announcement.userInfo,
readCount: announcement.readCount,
}));
});
}

View File

@@ -5,9 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
import type { AnnouncementsRepository } from '@/models/index.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
export const meta = {
tags: ['admin'],
@@ -35,6 +36,8 @@ export const paramDef = {
display: { type: 'string', enum: ['normal', 'banner', 'dialog'] },
forExistingUsers: { type: 'boolean' },
needConfirmationToRead: { type: 'boolean' },
closeDuration: { type: 'number', default: 0 },
displayOrder: { type: 'number', default: 0 },
isActive: { type: 'boolean' },
},
required: ['id'],
@@ -46,24 +49,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.update(announcement.id, {
updatedAt: new Date(),
title: ps.title,
text: ps.text,
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: ps.imageUrl || null,
display: ps.display,
icon: ps.icon,
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
isActive: ps.isActive,
});
await this.announcementService.update(announcement.id, ps);
});
}
}