Просмотр исходного кода

Merge branch 'feat/notification' into Imprv/GW-7123-create-watcher-model

Shun Miyazawa 4 лет назад
Родитель
Сommit
6a4f1b520d
1 измененных файлов с 218 добавлено и 0 удалено
  1. 218 0
      packages/app/src/server/models/notification.ts

+ 218 - 0
packages/app/src/server/models/notification.ts

@@ -0,0 +1,218 @@
+import {
+  Types, Document, Model, Schema /* , Query */, model,
+} from 'mongoose';
+import { subDays } from 'date-fns';
+import ActivityDefine from '../util/activityDefine';
+import loggerFactory from '~/utils/logger';
+import Crowi from '../crowi';
+import { ActivityDocument } from './activity';
+import User = require('./user');
+
+const logger = loggerFactory('growi:models:notification');
+
+const STATUS_UNREAD = 'UNREAD';
+const STATUS_UNOPENED = 'UNOPENED';
+const STATUS_OPENED = 'OPENED';
+const STATUSES = [STATUS_UNREAD, STATUS_UNOPENED, STATUS_OPENED];
+
+export interface NotificationDocument extends Document {
+  _id: Types.ObjectId
+  user: Types.ObjectId
+  targetModel: string
+  target: Types.ObjectId
+  action: string
+  activities: Types.ObjectId[]
+  status: string
+  createdAt: Date
+}
+
+export interface NotificationModel extends Model<NotificationDocument> {
+  findLatestNotificationsByUser(user: Types.ObjectId, skip: number, offset: number): Promise<NotificationDocument[]>
+  upsertByActivity(user: Types.ObjectId, activity: ActivityDocument, createdAt?: Date | null): Promise<NotificationDocument | null>
+  removeActivity(activity: any): any
+  // commented out type 'Query' temporary to avoid ts error
+  removeEmpty()/* : Query<any> */
+  read(user: typeof User) /* : Promise<Query<any>> */
+
+  open(user: typeof User, id: Types.ObjectId): Promise<NotificationDocument | null>
+  getUnreadCountByUser(user: Types.ObjectId): Promise<number | undefined>
+
+  STATUS_UNREAD: string
+  STATUS_UNOPENED: string
+  STATUS_OPENED: string
+}
+
+export default (crowi: Crowi) => {
+  const notificationEvent = crowi.event('Notification');
+
+  const notificationSchema = new Schema<NotificationDocument, NotificationModel>({
+    user: {
+      type: Schema.Types.ObjectId,
+      ref: 'User',
+      index: true,
+      require: true,
+    },
+    targetModel: {
+      type: String,
+      require: true,
+      enum: ActivityDefine.getSupportTargetModelNames(),
+    },
+    target: {
+      type: Schema.Types.ObjectId,
+      refPath: 'targetModel',
+      require: true,
+    },
+    action: {
+      type: String,
+      require: true,
+      enum: ActivityDefine.getSupportActionNames(),
+    },
+    activities: [
+      {
+        type: Schema.Types.ObjectId,
+        ref: 'Activity',
+      },
+    ],
+    status: {
+      type: String,
+      default: STATUS_UNREAD,
+      enum: STATUSES,
+      index: true,
+      require: true,
+    },
+    createdAt: {
+      type: Date,
+      default: Date.now,
+    },
+  });
+  notificationSchema.virtual('actionUsers').get(function(this: NotificationDocument) {
+    const Activity = crowi.model('Activity');
+    return Activity.getActionUsersFromActivities((this.activities as any) as ActivityDocument[]);
+  });
+  const transform = (doc, ret) => {
+    // delete ret.activities
+  };
+  notificationSchema.set('toObject', { virtuals: true, transform });
+  notificationSchema.set('toJSON', { virtuals: true, transform });
+  notificationSchema.index({
+    user: 1, target: 1, action: 1, createdAt: 1,
+  });
+
+  notificationSchema.statics.findLatestNotificationsByUser = function(user, limitNum, offset) {
+    const limit = limitNum || 10;
+
+    return Notification.find({ user })
+      .sort({ createdAt: -1 })
+      .skip(offset)
+      .limit(limit)
+      .populate(['user', 'target'])
+      .populate({ path: 'activities', populate: { path: 'user' } })
+      .exec();
+  };
+
+  notificationSchema.statics.upsertByActivity = async function(user, activity, createdAt = null) {
+    const {
+      _id: activityId, targetModel, target, action,
+    } = activity;
+
+    const now = createdAt || Date.now();
+    const lastWeek = subDays(now, 7);
+    const query = {
+      user, target, action, createdAt: { $gt: lastWeek },
+    };
+    const parameters = {
+      user,
+      targetModel,
+      target,
+      action,
+      status: STATUS_UNREAD,
+      createdAt: now,
+      $addToSet: { activities: activityId },
+    };
+
+    const options = {
+      upsert: true,
+      new: true,
+      setDefaultsOnInsert: true,
+      runValidators: true,
+    };
+
+    const notification = await Notification.findOneAndUpdate(query, parameters, options);
+
+    if (notification) {
+      notificationEvent.emit('update', notification.user);
+    }
+
+    return notification;
+  };
+
+  notificationSchema.statics.removeActivity = async function(activity) {
+    const { _id, target, action } = activity;
+    const query = { target, action };
+    const parameters = { $pull: { activities: _id } };
+
+    const result = await Notification.updateMany(query, parameters);
+
+    await Notification.removeEmpty();
+    return result;
+  };
+
+  notificationSchema.statics.removeEmpty = function() {
+    return Notification.deleteMany({ activities: { $size: 0 } });
+  };
+
+  notificationSchema.statics.read = async function(user) {
+    const query = { user, status: STATUS_UNREAD };
+    const parameters = { status: STATUS_UNOPENED };
+
+    return Notification.updateMany(query, parameters);
+  };
+
+  notificationSchema.statics.open = async function(user, id) {
+    const query = { _id: id, user: user._id };
+    const parameters = { status: STATUS_OPENED };
+    const options = { new: true };
+
+    const notification = await Notification.findOneAndUpdate(query, parameters, options);
+    if (notification) {
+      notificationEvent.emit('update', notification.user);
+    }
+    return notification;
+  };
+
+  notificationSchema.statics.getUnreadCountByUser = async function(user) {
+    const query = { user, status: STATUS_UNREAD };
+
+    try {
+      const count = await Notification.countDocuments(query);
+
+      return count;
+    }
+    catch (err) {
+      logger.error('Error on getUnreadCountByUser', err);
+      throw err;
+    }
+  };
+
+  notificationEvent.on('update', (user) => {
+    // TODO: be able to use getIo method by GW7221
+    // const io = crowi.getIo();
+    // if (io) {
+    //   io.sockets.emit('notification updated', { user });
+    // }
+  });
+
+  notificationSchema.statics.STATUS_UNOPENED = function() {
+    return STATUS_UNOPENED;
+  };
+  notificationSchema.statics.STATUS_UNREAD = function() {
+    return STATUS_UNREAD;
+  };
+  notificationSchema.statics.STATUS_OPENED = function() {
+    return STATUS_OPENED;
+  };
+
+  const Notification = model<NotificationDocument, NotificationModel>('Notification', notificationSchema);
+
+  return Notification;
+};