mirror of
https://github.com/MisskeyIO/misskey
synced 2024-12-27 21:18:13 +09:00
fix AnnouncementService
This commit is contained in:
parent
3a57fb2b4b
commit
50d246a1c0
@ -14,7 +14,6 @@ import { Packed } from '@/misc/json-schema.js';
|
|||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -31,7 +30,6 @@ export class AnnouncementService {
|
|||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private announcementEntityService: AnnouncementEntityService,
|
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
@ -99,7 +97,7 @@ export class AnnouncementService {
|
|||||||
'announcement."createdAt"': 'DESC',
|
'announcement."createdAt"': 'DESC',
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.announcementEntityService.packMany(
|
return this.packMany(
|
||||||
await query
|
await query
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@ -109,13 +107,22 @@ export class AnnouncementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUnreadAnnouncements(user: MiUser): Promise<MiAnnouncement[]> {
|
public async getUnreadAnnouncements(user: MiUser): Promise<Packed<'Announcement'>[]> {
|
||||||
const readsQuery = this.announcementReadsRepository.createQueryBuilder('read')
|
const query = this.announcementsRepository.createQueryBuilder('announcement');
|
||||||
.select('read.announcementId')
|
query.leftJoin(
|
||||||
.where('read.userId = :userId', { userId: user.id });
|
MiAnnouncementRead,
|
||||||
|
'read',
|
||||||
|
'read."announcementId" = announcement.id AND read."userId" = :userId',
|
||||||
|
{ userId: user.id },
|
||||||
|
);
|
||||||
|
query.select([
|
||||||
|
'announcement.*',
|
||||||
|
'CASE WHEN read.id IS NULL THEN FALSE ELSE TRUE END as "isRead"',
|
||||||
|
]);
|
||||||
|
query.andWhere('read.id IS NULL');
|
||||||
|
query.andWhere('announcement."isActive" = true');
|
||||||
|
|
||||||
const q = this.announcementsRepository.createQueryBuilder('announcement')
|
query
|
||||||
.where('announcement.isActive = true')
|
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb.orWhere('announcement.userId = :userId', { userId: user.id });
|
qb.orWhere('announcement.userId = :userId', { userId: user.id });
|
||||||
qb.orWhere('announcement.userId IS NULL');
|
qb.orWhere('announcement.userId IS NULL');
|
||||||
@ -123,17 +130,18 @@ export class AnnouncementService {
|
|||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb.orWhere('announcement.forExistingUsers = false');
|
qb.orWhere('announcement.forExistingUsers = false');
|
||||||
qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt });
|
qb.orWhere('announcement.createdAt > :createdAt', { createdAt: user.createdAt });
|
||||||
}))
|
}));
|
||||||
.andWhere(`announcement.id NOT IN (${ readsQuery.getQuery() })`);
|
|
||||||
|
|
||||||
q.setParameters(readsQuery.getParameters());
|
query.orderBy({
|
||||||
q.orderBy({
|
|
||||||
'announcement."isActive"': 'DESC',
|
'announcement."isActive"': 'DESC',
|
||||||
'announcement."displayOrder"': 'DESC',
|
'announcement."displayOrder"': 'DESC',
|
||||||
'announcement."createdAt"': 'DESC',
|
'announcement."createdAt"': 'DESC',
|
||||||
});
|
});
|
||||||
|
|
||||||
return q.getMany();
|
return this.packMany(
|
||||||
|
await query.getMany(),
|
||||||
|
user,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -239,6 +247,13 @@ export class AnnouncementService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async update(announcement: MiAnnouncement, values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<void> {
|
public async update(announcement: MiAnnouncement, values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<void> {
|
||||||
|
if (announcement.userId && announcement.userId !== values.userId) {
|
||||||
|
await this.announcementReadsRepository.delete({
|
||||||
|
announcementId: announcement.id,
|
||||||
|
userId: announcement.userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await this.announcementsRepository.update(announcement.id, {
|
await this.announcementsRepository.update(announcement.id, {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
title: values.title,
|
title: values.title,
|
||||||
@ -252,6 +267,7 @@ export class AnnouncementService {
|
|||||||
closeDuration: values.closeDuration,
|
closeDuration: values.closeDuration,
|
||||||
displayOrder: values.displayOrder,
|
displayOrder: values.displayOrder,
|
||||||
isActive: values.isActive,
|
isActive: values.isActive,
|
||||||
|
userId: values.userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id });
|
const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id });
|
||||||
@ -279,6 +295,9 @@ export class AnnouncementService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise<void> {
|
public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise<void> {
|
||||||
|
await this.announcementReadsRepository.delete({
|
||||||
|
announcementId: announcement.id,
|
||||||
|
});
|
||||||
await this.announcementsRepository.delete(announcement.id);
|
await this.announcementsRepository.delete(announcement.id);
|
||||||
|
|
||||||
if (moderator) {
|
if (moderator) {
|
||||||
@ -296,6 +315,37 @@ export class AnnouncementService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async countUnreadAnnouncements(me: MiUser): Promise<number> {
|
||||||
|
const query = this.announcementsRepository.createQueryBuilder('announcement');
|
||||||
|
query.leftJoinAndSelect(
|
||||||
|
MiAnnouncementRead,
|
||||||
|
'read',
|
||||||
|
'read."announcementId" = announcement.id AND read."userId" = :userId',
|
||||||
|
{ userId: me.id },
|
||||||
|
);
|
||||||
|
query.andWhere('read.id IS NULL');
|
||||||
|
query.andWhere('announcement."isActive" = true');
|
||||||
|
|
||||||
|
query
|
||||||
|
.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.orWhere('announcement."userId" = :userId', { userId: me.id });
|
||||||
|
qb.orWhere('announcement."userId" IS NULL');
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.orWhere('announcement."forExistingUsers" = false');
|
||||||
|
qb.orWhere('announcement."createdAt" > :createdAt', {
|
||||||
|
createdAt: me.createdAt,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
|
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@ -309,20 +359,16 @@ export class AnnouncementService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((await this.getUnreadAnnouncements(user)).length === 0) {
|
if ((await this.countUnreadAnnouncements(user)) === 0) {
|
||||||
this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
|
this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packMany(
|
public async packMany(
|
||||||
announcements: MiAnnouncement[],
|
announcements: (MiAnnouncement & { isRead?: boolean | null })[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
options?: {
|
|
||||||
reads?: MiAnnouncementRead[];
|
|
||||||
},
|
|
||||||
): Promise<Packed<'Announcement'>[]> {
|
): Promise<Packed<'Announcement'>[]> {
|
||||||
const reads = me ? (options?.reads ?? await this.getReads(me.id)) : [];
|
|
||||||
return announcements.map(announcement => ({
|
return announcements.map(announcement => ({
|
||||||
id: announcement.id,
|
id: announcement.id,
|
||||||
createdAt: announcement.createdAt.toISOString(),
|
createdAt: announcement.createdAt.toISOString(),
|
||||||
@ -336,7 +382,7 @@ export class AnnouncementService {
|
|||||||
closeDuration: announcement.closeDuration,
|
closeDuration: announcement.closeDuration,
|
||||||
displayOrder: announcement.displayOrder,
|
displayOrder: announcement.displayOrder,
|
||||||
forYou: announcement.userId === me?.id,
|
forYou: announcement.userId === me?.id,
|
||||||
isRead: reads.some(read => read.announcementId === announcement.id),
|
isRead: announcement.isRead ?? undefined,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@ import PerUserDriveChart from './chart/charts/per-user-drive.js';
|
|||||||
import ApRequestChart from './chart/charts/ap-request.js';
|
import ApRequestChart from './chart/charts/ap-request.js';
|
||||||
import { ChartManagementService } from './chart/ChartManagementService.js';
|
import { ChartManagementService } from './chart/ChartManagementService.js';
|
||||||
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
|
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
|
||||||
import { AnnouncementEntityService } from './entities/AnnouncementEntityService.js';
|
|
||||||
import { AntennaEntityService } from './entities/AntennaEntityService.js';
|
import { AntennaEntityService } from './entities/AntennaEntityService.js';
|
||||||
import { AppEntityService } from './entities/AppEntityService.js';
|
import { AppEntityService } from './entities/AppEntityService.js';
|
||||||
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
|
||||||
@ -203,7 +202,6 @@ const $ApRequestChart: Provider = { provide: 'ApRequestChart', useExisting: ApRe
|
|||||||
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
|
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
|
||||||
|
|
||||||
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
|
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
|
||||||
const $AnnouncementEntityService: Provider = { provide: 'AnnouncementEntityService', useExisting: AnnouncementEntityService };
|
|
||||||
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
|
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
|
||||||
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
||||||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||||
@ -332,7 +330,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
ApRequestChart,
|
ApRequestChart,
|
||||||
ChartManagementService,
|
ChartManagementService,
|
||||||
AbuseUserReportEntityService,
|
AbuseUserReportEntityService,
|
||||||
AnnouncementEntityService,
|
|
||||||
AntennaEntityService,
|
AntennaEntityService,
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
@ -456,7 +453,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$ApRequestChart,
|
$ApRequestChart,
|
||||||
$ChartManagementService,
|
$ChartManagementService,
|
||||||
$AbuseUserReportEntityService,
|
$AbuseUserReportEntityService,
|
||||||
$AnnouncementEntityService,
|
|
||||||
$AntennaEntityService,
|
$AntennaEntityService,
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
@ -580,7 +576,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
ApRequestChart,
|
ApRequestChart,
|
||||||
ChartManagementService,
|
ChartManagementService,
|
||||||
AbuseUserReportEntityService,
|
AbuseUserReportEntityService,
|
||||||
AnnouncementEntityService,
|
|
||||||
AntennaEntityService,
|
AntennaEntityService,
|
||||||
AppEntityService,
|
AppEntityService,
|
||||||
AuthSessionEntityService,
|
AuthSessionEntityService,
|
||||||
@ -703,7 +698,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$ApRequestChart,
|
$ApRequestChart,
|
||||||
$ChartManagementService,
|
$ChartManagementService,
|
||||||
$AbuseUserReportEntityService,
|
$AbuseUserReportEntityService,
|
||||||
$AnnouncementEntityService,
|
|
||||||
$AntennaEntityService,
|
$AntennaEntityService,
|
||||||
$AppEntityService,
|
$AppEntityService,
|
||||||
$AuthSessionEntityService,
|
$AuthSessionEntityService,
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type {
|
|
||||||
AnnouncementReadsRepository,
|
|
||||||
AnnouncementsRepository,
|
|
||||||
MiAnnouncement,
|
|
||||||
MiUser,
|
|
||||||
} from '@/models/_.js';
|
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AnnouncementEntityService {
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.announcementsRepository)
|
|
||||||
private announcementsRepository: AnnouncementsRepository,
|
|
||||||
|
|
||||||
@Inject(DI.announcementReadsRepository)
|
|
||||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async pack(
|
|
||||||
src: MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null },
|
|
||||||
me: { id: MiUser['id'] } | null | undefined,
|
|
||||||
): Promise<Packed<'Announcement'>> {
|
|
||||||
const announcement = typeof src === 'object'
|
|
||||||
? src
|
|
||||||
: await this.announcementsRepository.findOneByOrFail({
|
|
||||||
id: src,
|
|
||||||
}) as MiAnnouncement & { isRead?: boolean | null };
|
|
||||||
|
|
||||||
if (me && announcement.isRead === undefined) {
|
|
||||||
announcement.isRead = await this.announcementReadsRepository.countBy({
|
|
||||||
announcementId: announcement.id,
|
|
||||||
userId: me.id,
|
|
||||||
}).then(count => count > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: announcement.id,
|
|
||||||
createdAt: announcement.createdAt.toISOString(),
|
|
||||||
updatedAt: announcement.updatedAt?.toISOString() ?? null,
|
|
||||||
title: announcement.title,
|
|
||||||
text: announcement.text,
|
|
||||||
imageUrl: announcement.imageUrl,
|
|
||||||
icon: announcement.icon,
|
|
||||||
display: announcement.display,
|
|
||||||
forYou: announcement.userId === me?.id,
|
|
||||||
needConfirmationToRead: announcement.needConfirmationToRead,
|
|
||||||
closeDuration: announcement.closeDuration,
|
|
||||||
displayOrder: announcement.displayOrder,
|
|
||||||
isRead: announcement.isRead !== null ? announcement.isRead : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async packMany(
|
|
||||||
announcements: (MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null } | MiAnnouncement)[],
|
|
||||||
me: { id: MiUser['id'] } | null | undefined,
|
|
||||||
) : Promise<Packed<'Announcement'>[]> {
|
|
||||||
return (await Promise.allSettled(announcements.map(x => this.pack(x, me))))
|
|
||||||
.filter(result => result.status === 'fulfilled')
|
|
||||||
.map(result => (result as PromiseFulfilledResult<Packed<'Announcement'>>).value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,13 +3,9 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Brackets } from 'typeorm';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
|
||||||
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
import { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/_.js';
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
@ -40,13 +36,6 @@ export const paramDef = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.announcementsRepository)
|
|
||||||
private announcementsRepository: AnnouncementsRepository,
|
|
||||||
|
|
||||||
@Inject(DI.announcementReadsRepository)
|
|
||||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
|
||||||
|
|
||||||
private queryService: QueryService,
|
|
||||||
private announcementService: AnnouncementService,
|
private announcementService: AnnouncementService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user