From f6bf7f992a78e54d86a4701dbd1e4fda7ef4eb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Fri, 24 May 2024 21:40:28 +0900 Subject: [PATCH] =?UTF-8?q?feat(announcement):=20=E5=80=8B=E5=88=A5?= =?UTF-8?q?=E3=81=AE=E3=81=8A=E7=9F=A5=E3=82=89=E3=81=9B=E3=81=AB=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=AF=E3=81=A7=E9=A3=9B=E3=81=B9=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(MisskeyIO#639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/core/AnnouncementService.ts | 20 ++- .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 2 + .../src/server/api/endpoints/announcement.ts | 54 +++++++ packages/frontend/src/pages/announcement.vue | 139 ++++++++++++++++++ packages/frontend/src/pages/announcements.vue | 11 +- packages/frontend/src/router/definition.ts | 3 + packages/misskey-js/etc/misskey-js.api.md | 8 + .../misskey-js/src/autogen/apiClientJSDoc.ts | 11 ++ packages/misskey-js/src/autogen/endpoint.ts | 3 + packages/misskey-js/src/autogen/entities.ts | 2 + packages/misskey-js/src/autogen/types.ts | 63 ++++++++ 12 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/announcement.ts create mode 100644 packages/frontend/src/pages/announcement.vue diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 102d256be..394b47c99 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { Brackets, In } from 'typeorm'; +import { Brackets, EntityNotFoundError, In } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiUser } from '@/models/User.js'; import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, UsersRepository } from '@/models/_.js'; @@ -254,6 +254,24 @@ export class AnnouncementService { } } + @bindThis + public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise> { + const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId }); + if (me) { + if (announcement.userId && announcement.userId !== me.id) { + throw new EntityNotFoundError(this.announcementsRepository.metadata.target, { id: announcementId }); + } + + const read = await this.announcementReadsRepository.findOneBy({ + announcementId: announcement.id, + userId: me.id, + }); + return this.announcementEntityService.pack({ ...announcement, isRead: read !== null }, me); + } else { + return this.announcementEntityService.pack(announcement, null); + } + } + @bindThis public async getAnnouncements( me: MiUser | null, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index e37e95c1d..fcd5059a5 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -95,6 +95,7 @@ import * as ep___admin_sso_create from './endpoints/admin/sso/create.js'; import * as ep___admin_sso_delete from './endpoints/admin/sso/delete.js'; import * as ep___admin_sso_list from './endpoints/admin/sso/list.js'; import * as ep___admin_sso_update from './endpoints/admin/sso/update.js'; +import * as ep___announcement from './endpoints/announcement.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js'; @@ -484,6 +485,7 @@ const $admin_sso_create: Provider = { provide: 'ep:admin/sso/create', useClass: const $admin_sso_delete: Provider = { provide: 'ep:admin/sso/delete', useClass: ep___admin_sso_delete.default }; const $admin_sso_list: Provider = { provide: 'ep:admin/sso/list', useClass: ep___admin_sso_list.default }; const $admin_sso_update: Provider = { provide: 'ep:admin/sso/update', useClass: ep___admin_sso_update.default }; +const $announcement: Provider = { provide: 'ep:announcement', useClass: ep___announcement.default }; const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default }; @@ -877,6 +879,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_sso_delete, $admin_sso_list, $admin_sso_update, + $announcement, $announcements, $antennas_create, $antennas_delete, @@ -1264,6 +1267,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_sso_delete, $admin_sso_list, $admin_sso_update, + $announcement, $announcements, $antennas_create, $antennas_delete, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 862d1d162..57322d3d5 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -95,6 +95,7 @@ import * as ep___admin_sso_create from './endpoints/admin/sso/create.js'; import * as ep___admin_sso_delete from './endpoints/admin/sso/delete.js'; import * as ep___admin_sso_list from './endpoints/admin/sso/list.js'; import * as ep___admin_sso_update from './endpoints/admin/sso/update.js'; +import * as ep___announcement from './endpoints/announcement.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; import * as ep___antennas_delete from './endpoints/antennas/delete.js'; @@ -482,6 +483,7 @@ const eps = [ ['admin/sso/delete', ep___admin_sso_delete], ['admin/sso/list', ep___admin_sso_list], ['admin/sso/update', ep___admin_sso_update], + ['announcement', ep___announcement], ['announcements', ep___announcements], ['antennas/create', ep___antennas_create], ['antennas/delete', ep___antennas_delete], diff --git a/packages/backend/src/server/api/endpoints/announcement.ts b/packages/backend/src/server/api/endpoints/announcement.ts new file mode 100644 index 000000000..98b1746ae --- /dev/null +++ b/packages/backend/src/server/api/endpoints/announcement.ts @@ -0,0 +1,54 @@ +/* + * 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 { AnnouncementService } from '@/core/AnnouncementService.js'; +import { EntityNotFoundError } from "typeorm"; +import { ApiError } from "../error.js"; + +export const meta = { + tags: ['meta'], + + requireCredential: false, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'Announcement', + }, + + errors: { + noSuchAnnouncement: { + message: 'No such announcement.', + code: 'NO_SUCH_ANNOUNCEMENT', + id: 'b57b5e1d-4f49-404a-9edb-46b00268f121', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + announcementId: { type: 'string', format: 'misskey:id' }, + }, + required: ['announcementId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private announcementService: AnnouncementService, + ) { + super(meta, paramDef, async (ps, me) => { + try { + return await this.announcementService.getAnnouncement(ps.announcementId, me); + } catch (err) { + if (err instanceof EntityNotFoundError) throw new ApiError(meta.errors.noSuchAnnouncement); + throw err; + } + }); + } +} diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue new file mode 100644 index 000000000..45c6b4cf4 --- /dev/null +++ b/packages/frontend/src/pages/announcement.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 01b21e680..4ee7b27c1 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -26,9 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-
- -
+ +
+ {{ i18n.ts.createdAt }}: +
+
+ {{ i18n.ts.updatedAt }}: +
+
{{ i18n.ts.gotIt }} diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index 590ca0a2c..e7f426bf2 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -197,6 +197,9 @@ const routes: RouteDef[] = [{ }, { path: '/announcements', component: page(() => import('@/pages/announcements.vue')), +}, { + path: '/announcements/:announcementId', + component: page(() => import('@/pages/announcement.vue')), }, { path: '/about', component: page(() => import('@/pages/about.vue')), diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 9586e5193..3ad240bb1 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -393,6 +393,12 @@ type AnnouncementCreated = { announcement: Announcement; }; +// @public (undocumented) +type AnnouncementRequest = operations['announcement']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AnnouncementResponse = operations['announcement']['responses']['200']['content']['application/json']; + // @public (undocumented) type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; @@ -1308,6 +1314,8 @@ declare namespace entities { AdminSsoListRequest, AdminSsoListResponse, AdminSsoUpdateRequest, + AnnouncementRequest, + AnnouncementResponse, AnnouncementsRequest, AnnouncementsResponse, AntennasCreateRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 008251bbc..3d65d9ddb 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -983,6 +983,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 7c5205bc4..518d5f0e0 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -119,6 +119,8 @@ import type { AdminSsoListRequest, AdminSsoListResponse, AdminSsoUpdateRequest, + AnnouncementRequest, + AnnouncementResponse, AnnouncementsRequest, AnnouncementsResponse, AntennasCreateRequest, @@ -670,6 +672,7 @@ export type Endpoints = { 'admin/sso/delete': { req: AdminSsoDeleteRequest; res: EmptyResponse }; 'admin/sso/list': { req: AdminSsoListRequest; res: AdminSsoListResponse }; 'admin/sso/update': { req: AdminSsoUpdateRequest; res: EmptyResponse }; + 'announcement': { req: AnnouncementRequest; res: AnnouncementResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; 'antennas/delete': { req: AntennasDeleteRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 5239627f0..595977631 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -122,6 +122,8 @@ export type AdminSsoDeleteRequest = operations['admin___sso___delete']['requestB export type AdminSsoListRequest = operations['admin___sso___list']['requestBody']['content']['application/json']; export type AdminSsoListResponse = operations['admin___sso___list']['responses']['200']['content']['application/json']; export type AdminSsoUpdateRequest = operations['admin___sso___update']['requestBody']['content']['application/json']; +export type AnnouncementRequest = operations['announcement']['requestBody']['content']['application/json']; +export type AnnouncementResponse = operations['announcement']['responses']['200']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; export type AntennasCreateRequest = operations['antennas___create']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index b02b770bf..f83f3c7d6 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -814,6 +814,15 @@ export type paths = { */ post: operations['admin___sso___update']; }; + '/announcement': { + /** + * announcement + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['announcement']; + }; '/announcements': { /** * announcements @@ -10751,6 +10760,60 @@ export type operations = { }; }; }; + /** + * announcement + * @description No description provided. + * + * **Credential required**: *No* + */ + announcement: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + announcementId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Announcement']; + }; + }; + /** @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']; + }; + }; + }; + }; /** * announcements * @description No description provided.