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 30 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
WNomunomu Apr 20, 2024
924d3e7
fix type
WNomunomu Apr 20, 2024
bbf853a
create user flow
WNomunomu 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,19 @@
import type {
IPage, IUser, IUserHasId, Ref,
} from '@growi/core';

import { type IAnnouncement, type ParamsForAnnouncement } from '~/interfaces/announcement';

import { apiv3Post } from '../../../../client/util/apiv3-client';
import { toastError } from '../../../../client/util/toastr';

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

try {
await apiv3Post('/announcement/doAnnouncement', 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/サービス層に分類されるので不適切

}

};
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 type { IAnnouncement } from '../../../../interfaces/announcement';
import { getOrCreateModel } from '../../../../server/util/mongoose-utils';
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) as IAnnouncement;
Copy link
Member

Choose a reason for hiding this comment

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

as を使わず記述してください

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as を消去し、修正しました。


return announcement;
};

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

export { Announcement };
79 changes: 79 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,79 @@
import type { IPage } from '@growi/core';

import { Announcement, AnnouncementStatuses } from '~/features/announcement';
import type { IAnnouncement, ParamsForAnnouncement } from '~/interfaces/announcement';

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

export default 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() => {};

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 しないといけない理由ある?


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);

};

}

module.exports = AnnouncementService;
Copy link
Member

Choose a reason for hiding this comment

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

ExternalAccountService を参考に、singleton instance をこのモジュールで export するようにしてください

Copy link
Contributor Author

Choose a reason for hiding this comment

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

singleton instanceをexportするようにしました。

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
15 changes: 8 additions & 7 deletions apps/app/src/interfaces/announcement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ import type {
IUser, IPage, Ref, HasObjectId,
} from '@growi/core';

import { AnnouncementStatuses } from '../features/announcement/server/events/announcement-utils';

type AnnouncementStatuses = typeof AnnouncementStatuses;
import type { AnnouncementStatusesType } from '../features/announcement/server/events/announcement-utils';

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

export type IAnnouncementHasId = IAnnouncement & HasObjectId;

export interface ParamsForAnnouncement extends Omit<IAnnouncement, 'receivers'> {
receivers: Ref<IUser>[]
}
miya marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 9 additions & 0 deletions apps/app/src/server/crowi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Crowi {
this.commentService = null;
this.questionnaireService = null;
this.questionnaireCronService = null;
this.announcementService = null;

this.tokens = null;

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

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

Crowi.prototype.setupAnnouncementService = async function() {
const AnnouncementService = require('../../features/announcement/server/service/announcement');
if (this.announcementService == null) {
this.announcementService = new AnnouncementService(this);
}
};

// execute after setupPassport
Crowi.prototype.setupExternalAccountService = function() {
instanciateExternalAccountService(this.passportService);
Expand Down
59 changes: 59 additions & 0 deletions apps/app/src/server/routes/apiv3/announcement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Router } from 'express';
import {
body, param, query, validationResult,
} from 'express-validator';

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


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 }),
body('comment').optional({ nullable: true }).isString(),
body('emoji').optional({ nullable: true }).isString(),
body('isReadReceiptTrackingEnabled').exists().isBoolean(),
body('pageId').exists({ checkFalsy: true }),
body('receivers').exists({ checkFalsy: true }).isArray(),
],
};

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

const params: ParamsForAnnouncement = req.body;

const page = await Page.findById(params.pageId);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

req.paramsではpageIdが正しく取得できなかったため、この形にしています。

Copy link
Member

Choose a reason for hiding this comment

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

クライアントのリクエスト方法がパスパラメーターで取得できるかたちになっていないのでは?
https://github.com/weseek/growi/pull/8715/files#diff-16e65c79c9018f437410fdf1211d572707905ec324e3b3129c795555aa364a5cR13

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/:id を用いることで、pageIdをパスパラメータで取得できるようにしました。


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);
miya marked this conversation as resolved.
Show resolved Hide resolved

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

});

return router;

};
miya marked this conversation as resolved.
Show resolved Hide resolved
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('./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
8 changes: 5 additions & 3 deletions apps/app/src/server/service/activity.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { IPage } from '@growi/core';
import mongoose from 'mongoose';

import type { IActivity, SupportedActionType } from '~/interfaces/activity';
import {
IActivity, SupportedActionType, AllSupportedActions, ActionGroupSize,
AllSupportedActions, ActionGroupSize,
AllEssentialActions, AllSmallGroupActions, AllMediumGroupActions, AllLargeGroupActions,
} from '~/interfaces/activity';
import Activity, { ActivityDocument } from '~/server/models/activity';
import type { ActivityDocument } from '~/server/models/activity';
import Activity from '~/server/models/activity';

import loggerFactory from '../../utils/logger';
import Crowi from '../crowi';
import type Crowi from '../crowi';


import type { GeneratePreNotify, GetAdditionalTargetUsers } from './pre-notify';
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/server/service/pre-notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
IPage, IUser, Ref,
} from '@growi/core';

import { ActivityDocument } from '../models/activity';
import type { ActivityDocument } from '../models/activity';
import Subscription from '../models/subscription';
import { getModelSafely } from '../util/mongoose-utils';

Expand Down