Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implementions of announcement creation function #8715

Open
wants to merge 42 commits into
base: feat/announcement-function
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6451ce7
create AnnouncementService
WNomunomu Nov 13, 2023
b2fee4e
Merge branch 'feat/announcement-function' into feat/134084-implementi…
WNomunomu Mar 30, 2024
a2b50b2
create createByParameters method
WNomunomu Apr 8, 2024
5b5ddb8
create upsertByActivity method and createAnnouncement method
WNomunomu Apr 8, 2024
26b3786
add supported model and action to activity
WNomunomu Apr 8, 2024
b9db316
create post api for announcement
WNomunomu Apr 12, 2024
4b9bcc0
fix login.js
WNomunomu Apr 12, 2024
7bc44a5
setup AnnouncementService in crowi
WNomunomu Apr 12, 2024
331794e
add module export of AnnouncementService
WNomunomu Apr 15, 2024
3064298
create button for test
Apr 20, 2024
924d3e7
fix type
Apr 20, 2024
bbf853a
create user flow
Apr 20, 2024
a6df789
Merge branch 'master' into feat/134084-implementions-of-announcement-…
WNomunomu May 4, 2024
9f992b9
Merge branch 'master' into feat/134084-implementions-of-announcement-…
WNomunomu May 24, 2024
f1b99be
add router.use to index.js
WNomunomu May 27, 2024
12fdce9
Merge branch 'feat/announcement-function' into feat/134084-implementi…
WNomunomu Jun 23, 2024
ba7fad9
trivial fix
WNomunomu Jun 23, 2024
c15a124
clean code
WNomunomu Jun 23, 2024
fc4662d
clean code
WNomunomu Jun 23, 2024
d9f2013
remove code for test
WNomunomu Jun 23, 2024
bf6a25d
change to use params instead of announcement
WNomunomu Jun 23, 2024
dbe2818
add announcement action to action group
WNomunomu Jun 23, 2024
c83a629
fix parameter and add await prefix
WNomunomu Jun 23, 2024
c88e3ef
change target model
WNomunomu Jun 23, 2024
ca53ba1
remove comment
WNomunomu Jun 24, 2024
7a28945
fix an array type
WNomunomu Jun 24, 2024
9cd6f18
Merge branch 'master' into feat/134084-implementions-of-announcement-…
WNomunomu Aug 5, 2024
f6de616
fix fb
WNomunomu Aug 5, 2024
a8a2761
add validators
WNomunomu Aug 5, 2024
950f254
Merge branch 'feat/announcement-function' into feat/134084-implementi…
WNomunomu Aug 6, 2024
7eb1c6c
Merge branch 'master' into feat/134084-implementions-of-announcement-…
WNomunomu Sep 9, 2024
c60ac35
implement to enable to get pageId with path parameters
WNomunomu Sep 9, 2024
d560f70
remove unnecessary parts
WNomunomu Sep 9, 2024
bd33ca5
relocate files related to announcement function
WNomunomu Sep 9, 2024
a87b1ca
change relative paths
WNomunomu Sep 9, 2024
615e657
Merge branch 'feat/announcement-function' into feat/134084-implementi…
WNomunomu Sep 9, 2024
1481713
revert the method for retrieving the pageId
WNomunomu Sep 12, 2024
6dc961f
add validation
WNomunomu Sep 12, 2024
7736dde
make insertAnnouncement method private method
WNomunomu Sep 29, 2024
f52a5f5
change the url to kebab-case
WNomunomu Sep 29, 2024
c6893d4
make AnnouncementService singleton
WNomunomu Sep 29, 2024
45e69ac
remove "as" statement
WNomunomu Sep 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { apiv3Post } from '../../../../client/util/apiv3-client';
import { toastError } from '../../../../client/util/toastr';
import { type ParamsForAnnouncement } from '../../interfaces/announcement';

export const createAnnouncement = async(params: ParamsForAnnouncement): Promise<void> => {

try {
await apiv3Post('/announcement/do-announcement', params);
}
catch (err) {
toastError(err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toastr の表示は view の責務
このユーティリティは DAO/サービス層に分類されるので不適切

}

};
25 changes: 25 additions & 0 deletions apps/app/src/features/announcement/interfaces/announcement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {
IUser, IPage, Ref, HasObjectId,
} from '@growi/core';

import type { AnnouncementStatusesType } from '../server/events/announcement-utils';

export interface IAnnouncement {
sender: Ref<IUser>
comment?: string
emoji?: string
isReadReceiptTrackingEnabled: boolean
pageId: Ref<IPage>
receivers:
{
receiver: Ref<IUser>,
updatedAt?: Date,
readStatus: AnnouncementStatusesType,
}[]
}

export type IAnnouncementHasId = IAnnouncement & HasObjectId;

export interface ParamsForAnnouncement extends Omit<IAnnouncement, 'receivers'> {
receivers: Ref<IUser>[]
}
83 changes: 83 additions & 0 deletions apps/app/src/features/announcement/routes/apiv3/announcement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { Router } from 'express';
import { body } from 'express-validator';

import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
import type { CrowiRequest } from '~/interfaces/crowi-request';
import type Crowi from '~/server/crowi';

import type { ParamsForAnnouncement } from '../../interfaces/announcement';
import { announcementService } from '../../server/service/announcement';


const express = require('express');

const router = express.Router();

module.exports = (crowi: Crowi): Router => {

const { Page } = crowi.models;

const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);

const validators = {
doAnnouncement: [
body('sender')
.exists({ checkFalsy: true })
.withMessage('sender is required')
.isMongoId()
.withMessage('sender must be mongo id'),
body('comment')
.optional({ nullable: true })
.isString()
.withMessage('comment must be string'),
body('emoji')
.optional({ nullable: true })
.isString()
.withMessage('emoji must be string'),
body('isReadReceiptTrackingEnabled')
.exists()
.withMessage('isReadReceiptTrackingEnabled is required')
.isBoolean()
.withMessage('isReadReceiptTrackingEnabled must be boolean'),
body('pageId')
.exists({ checkFalsy: true })
.withMessage('pageId is required')
.isMongoId()
.withMessage('pageId must be mongo id'),
body('receivers')
.exists({ checkFalsy: true })
.withMessage('receivers is required')
.isArray()
.withMessage('receivers must be an array'),
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • sender とか pageId など ObjectId でとるものは isMongoId() をつけよう
  • バリデーションに失敗した時にクライアントにエラーメッセージを返却するために .withMessage() をつけよう

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validatorに設定を追加させていただきました。

};

router.post('/do-announcement', loginRequiredStrictly, validators.doAnnouncement, async(req: CrowiRequest) => {

const params: ParamsForAnnouncement = req.body;

const pageId = params.pageId;

const page = await Page.findById(pageId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • req.user がアクセスできるページでないといけないので、取得はそれを考慮しているメソッドを使ってください
  • ページが見つからなかった場合は 4xx 系エラーを返す


const parametersForActivity = {
ip: req.ip,
endpoint: req.originalUrl,
user: req.user?._id,
target: page,
targetModel: SupportedTargetModel.MODEL_ANNOUNCEMENT,
action: SupportedAction.ACTION_USER_ANNOUNCE,
snapshot: {
username: req.user?.username,
},
};

const activity = await crowi.activityService.createActivity(parametersForActivity);

announcementService?.doAnnounce(activity, page, params);

});

return router;

};
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export const AnnouncementStatuses = {
STATUS_IGNORED: 'IGNORED',
} as const;

type AnnouncementStatuses = typeof AnnouncementStatuses[keyof typeof AnnouncementStatuses];
export type AnnouncementStatusesType = typeof AnnouncementStatuses[keyof typeof AnnouncementStatuses];
15 changes: 9 additions & 6 deletions apps/app/src/features/announcement/server/models/announcement.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {
Types, Document, Schema, Model,
} from 'mongoose';
import type { Types, Document, Model } from 'mongoose';
import { Schema } from 'mongoose';

import { IAnnouncement } from '../../../../interfaces/announcement';
import { getOrCreateModel } from '../../../../server/util/mongoose-utils';
import type { IAnnouncement } from '../../interfaces/announcement';
import { AnnouncementStatuses } from '../events/announcement-utils';

type AnnouncementStatuses = typeof AnnouncementStatuses;

export interface AnnouncementDocument extends IAnnouncement, Document {
_id: Types.ObjectId
}
Expand Down Expand Up @@ -58,6 +55,12 @@ const AnnouncementSchema = new Schema<AnnouncementDocument>({
],
}, {});

AnnouncementSchema.statics.createByParameters = async function(parameters): Promise<IAnnouncement> {
const announcement = await this.create(parameters);

return announcement;
};

const Announcement = getOrCreateModel<AnnouncementDocument, AnnouncementModel>('Announcement', AnnouncementSchema);

export { Announcement };
87 changes: 87 additions & 0 deletions apps/app/src/features/announcement/server/service/announcement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { IPage } from '@growi/core';

import { Announcement, AnnouncementStatuses } from '~/features/announcement';
import loggerFactory from '~/utils/logger';

import type Crowi from '../../../../server/crowi';
import type { ActivityDocument } from '../../../../server/models/activity';
import type { PreNotifyProps } from '../../../../server/service/pre-notify';
import type { IAnnouncement, ParamsForAnnouncement } from '../../interfaces/announcement';

const logger = loggerFactory('growi:service:inAppNotification');

class AnnouncementService {

crowi!: Crowi;

activityEvent: any;

constructor(crowi: Crowi) {
this.crowi = crowi;
this.activityEvent = crowi.event('activity');

this.getReadRate = this.getReadRate.bind(this);
this.insertAnnouncement = this.insertAnnouncement.bind(this);
this.doAnnounce = this.doAnnounce.bind(this);

}

getReadRate = async() => { };

private insertAnnouncement = async(
params: ParamsForAnnouncement,
): Promise<void> => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private を付けよう

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private method にしました。


const {
sender, comment, emoji, isReadReceiptTrackingEnabled, pageId, receivers,
} = params;

const announcement: IAnnouncement = {
sender,
comment,
emoji,
isReadReceiptTrackingEnabled,
pageId,
receivers: receivers.map((receiver) => {
return {
receiver,
readStatus: AnnouncementStatuses.STATUS_UNREAD,
};
}),
};

const operation = [{
insertOne: {
document: announcement,
},
}];

await Announcement.bulkWrite(operation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bulkWrite しないといけない理由ある?

logger.info('Announcement bulkWrite has run');

return;

};

doAnnounce = async(activity: ActivityDocument, target: IPage, params: ParamsForAnnouncement): Promise<void> => {

this.insertAnnouncement(params);

const preNotify = async(props: PreNotifyProps) => {

const { notificationTargetUsers } = props;

notificationTargetUsers?.push(...params.receivers);
};

this.activityEvent.emit('updated', activity, target, preNotify);

};

}

// eslint-disable-next-line import/no-mutable-exports
export let announcementService: AnnouncementService | undefined; // singleton instance
export function instanciate(crowi: Crowi): void {
announcementService = new AnnouncementService(crowi);
}
6 changes: 6 additions & 0 deletions apps/app/src/interfaces/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Ref, HasObjectId, IUser } from '@growi/core';
// Model
const MODEL_PAGE = 'Page';
const MODEL_USER = 'User';
const MODEL_ANNOUNCEMENT = 'Announcement';
const MODEL_COMMENT = 'Comment';

// Action
Expand All @@ -27,6 +28,7 @@ const ACTION_USER_PASSWORD_UPDATE = 'USER_PASSWORD_UPDATE';
const ACTION_USER_API_TOKEN_UPDATE = 'USER_API_TOKEN_UPDATE';
const ACTION_USER_EDITOR_SETTINGS_UPDATE = 'USER_EDITOR_SETTINGS_UPDATE';
const ACTION_USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE = 'USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE';
const ACTION_USER_ANNOUNCE = 'USER_ANNOUNCE';
const ACTION_PAGE_VIEW = 'PAGE_VIEW';
const ACTION_PAGE_USER_HOME_VIEW = 'PAGE_USER_HOME_VIEW';
const ACTION_PAGE_NOT_FOUND = 'PAGE_NOT_FOUND';
Expand Down Expand Up @@ -166,6 +168,7 @@ const ACTION_ADMIN_SEARCH_INDICES_REBUILD = 'ADMIN_SEARCH_INDICES_REBUILD';
export const SupportedTargetModel = {
MODEL_PAGE,
MODEL_USER,
MODEL_ANNOUNCEMENT,
} as const;

export const SupportedEventModel = {
Expand Down Expand Up @@ -206,6 +209,7 @@ export const SupportedAction = {
ACTION_USER_API_TOKEN_UPDATE,
ACTION_USER_EDITOR_SETTINGS_UPDATE,
ACTION_USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE,
ACTION_USER_ANNOUNCE,
ACTION_PAGE_VIEW,
ACTION_PAGE_USER_HOME_VIEW,
ACTION_PAGE_FORBIDDEN,
Expand Down Expand Up @@ -358,6 +362,7 @@ export const EssentialActionGroup = {
ACTION_PAGE_RECURSIVELY_REVERT,
ACTION_COMMENT_CREATE,
ACTION_USER_REGISTRATION_APPROVAL_REQUEST,
ACTION_USER_ANNOUNCE,
} as const;

export const ActionGroupSize = {
Expand Down Expand Up @@ -396,6 +401,7 @@ export const MediumActionGroup = {
ACTION_USER_API_TOKEN_UPDATE,
ACTION_USER_EDITOR_SETTINGS_UPDATE,
ACTION_USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE,
ACTION_USER_ANNOUNCE,
ACTION_PAGE_LIKE,
ACTION_PAGE_UNLIKE,
ACTION_PAGE_BOOKMARK,
Expand Down
24 changes: 0 additions & 24 deletions apps/app/src/interfaces/announcement.ts

This file was deleted.

9 changes: 8 additions & 1 deletion apps/app/src/server/crowi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import QuestionnaireCronService from '~/features/questionnaire/server/service/qu
import loggerFactory from '~/utils/logger';
import { projectRoot } from '~/utils/project-dir-utils';

import { instanciate as instanciateAnnouncementService } from '../../features/announcement/server/service/announcement';
import UserEvent from '../events/user';
import { aclService as aclServiceSingletonInstance } from '../service/acl';
import AppService from '../service/app';
import AttachmentService from '../service/attachment';
import { configManager as configManagerSingletonInstance } from '../service/config-manager';
import { instanciate as instanciateExternalAccountService } from '../service/external-account';
import { instanciate, instanciate as instanciateExternalAccountService } from '../service/external-account';
import { FileUploader, getUploader } from '../service/file-uploader'; // eslint-disable-line no-unused-vars
import { G2GTransferPusherService, G2GTransferReceiverService } from '../service/g2g-transfer';
import { initializeImportService } from '../service/import';
Expand Down Expand Up @@ -102,6 +103,7 @@ class Crowi {
this.commentService = null;
this.questionnaireService = null;
this.questionnaireCronService = null;
this.announcementService = null;

this.tokens = null;

Expand Down Expand Up @@ -167,6 +169,7 @@ Crowi.prototype.init = async function() {
this.setupSyncPageStatusService(),
this.setupQuestionnaireService(),
this.setUpCustomize(), // depends on pluginService
this.setupAnnouncementService(),
]);

await Promise.all([
Expand Down Expand Up @@ -762,6 +765,10 @@ Crowi.prototype.setupG2GTransferService = async function() {
}
};

Crowi.prototype.setupAnnouncementService = async function() {
instanciateAnnouncementService(this);
};

// execute after setupPassport
Crowi.prototype.setupExternalAccountService = function() {
instanciateExternalAccountService(this.passportService);
Expand Down
2 changes: 2 additions & 0 deletions apps/app/src/server/routes/apiv3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ module.exports = (crowi, app) => {
router.use('/bookmarks', require('./bookmarks')(crowi));
router.use('/attachment', require('./attachment')(crowi));

router.use('/announcement', require('../../../features/announcement/routes/apiv3/announcement')(crowi));

router.use('/slack-integration', require('./slack-integration')(crowi));

router.use('/staffs', require('./staffs')(crowi));
Expand Down
4 changes: 3 additions & 1 deletion apps/app/src/server/routes/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ module.exports = function(crowi, app) {
const preNotify = async(props) => {
const adminUsers = await User.findAdmins();

props.push(...adminUsers);
const { notificationTargetUsers } = props;

notificationTargetUsers.push(...adminUsers);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは現存する何かしらのバグの修正?

};

await activityEvent.emit('updated', activity, user, preNotify);
Expand Down
Loading