feat: 個人宛てお知らせ機能 (#107)
* feat: 個人宛てお知らせ機能 * Remove unused import * Update packages/backend/src/server/api/endpoints/admin/announcements/create.ts Co-authored-by: riku6460 <17585784+riku6460@users.noreply.github.com> * Update packages/frontend/src/pages/announcements.vue Co-authored-by: riku6460 <17585784+riku6460@users.noreply.github.com> * Restore breakline * 一般向けAPIにはuserオブジェクトを提供しない * fix * Fix * Update packages/misskey-js/src/entities.ts Co-authored-by: riku6460 <17585784+riku6460@users.noreply.github.com> * Fix * Update misskey-js.api.md * Fix lint * 他のテーブルに合わせて character varying(32) にした * count クエリを1つにまとめた * user を pack するようにした * いろいろ修正 * 個人宛てのお知らせの表示を改善 * Update misskey-js.api.md * Merge migration scripts * Fix * Update packages/backend/migration/1688647797135-userannouncement.js Co-authored-by: riku6460 <17585784+riku6460@users.noreply.github.com> * Update packages/backend/src/models/entities/Announcement.ts Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> * Fix * Update migration script --------- Co-authored-by: CyberRex <hspwinx86@gmail.com> Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
This commit is contained in:
@@ -42,6 +42,14 @@ export const meta = {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
closeDuration: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -52,6 +60,8 @@ export const paramDef = {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
text: { type: 'string', minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 1 },
|
||||
userId: { type: 'string', nullable: true, format: 'misskey:id' },
|
||||
closeDuration: { type: 'number', nullable: false },
|
||||
},
|
||||
required: ['title', 'text', 'imageUrl'],
|
||||
} as const;
|
||||
@@ -73,6 +83,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
title: ps.title,
|
||||
text: ps.text,
|
||||
imageUrl: ps.imageUrl,
|
||||
userId: ps.userId ?? null,
|
||||
closeDuration: ps.closeDuration,
|
||||
}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null });
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/index.js';
|
||||
import { In } from 'typeorm';
|
||||
import type { AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Announcement } from '@/models/entities/Announcement.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -46,10 +48,23 @@ export const meta = {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
reads: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
closeDuration: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -61,6 +76,7 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -75,10 +91,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private queryService: QueryService,
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
|
||||
const builder = this.announcementsRepository.createQueryBuilder('announcement');
|
||||
if (ps.userId) {
|
||||
builder.where('"userId" = :userId', { userId: ps.userId });
|
||||
} else {
|
||||
builder.where('"userId" IS NULL');
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(builder, ps.sinceId, ps.untilId);
|
||||
|
||||
const announcements = await query.limit(ps.limit).getMany();
|
||||
|
||||
@@ -90,6 +117,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}));
|
||||
}
|
||||
|
||||
const users = await this.usersRepository.findBy({
|
||||
id: In(announcements.map(a => a.userId).filter(id => id != null)),
|
||||
});
|
||||
const packedUsers = await this.userEntityService.packMany(users, me, {
|
||||
detail: false,
|
||||
});
|
||||
|
||||
return announcements.map(announcement => ({
|
||||
id: announcement.id,
|
||||
createdAt: announcement.createdAt.toISOString(),
|
||||
@@ -97,7 +131,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
title: announcement.title,
|
||||
text: announcement.text,
|
||||
imageUrl: announcement.imageUrl,
|
||||
userId: announcement.userId,
|
||||
user: packedUsers.find(user => user.id === announcement.userId),
|
||||
reads: reads.get(announcement)!,
|
||||
closeDuration: announcement.closeDuration,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AnnouncementsRepository } from '@/models/index.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
@@ -26,8 +26,10 @@ export const paramDef = {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
text: { type: 'string', minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 0 },
|
||||
userId: { type: 'string', nullable: true, format: 'misskey:id' },
|
||||
closeDuration: { type: 'number', nullable: false },
|
||||
},
|
||||
required: ['id', 'title', 'text', 'imageUrl'],
|
||||
required: ['id', 'title', 'text', 'imageUrl', 'closeDuration'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -36,18 +38,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementsReadsRepository: AnnouncementReadsRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
|
||||
|
||||
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
|
||||
|
||||
if (announcement.userId && announcement.userId !== ps.userId) {
|
||||
await this.announcementsReadsRepository.delete({ id: announcement.id, userId: announcement.userId });
|
||||
}
|
||||
|
||||
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,
|
||||
userId: ps.userId ?? null,
|
||||
closeDuration: ps.closeDuration,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user