bookmark.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import type { Document, Model, Types } from 'mongoose';
  2. import { Schema } from 'mongoose';
  3. import mongoosePaginate from 'mongoose-paginate-v2';
  4. import uniqueValidator from 'mongoose-unique-validator';
  5. import type { IBookmark } from '~/interfaces/bookmark-info';
  6. import loggerFactory from '~/utils/logger';
  7. import type Crowi from '../crowi';
  8. import { getOrCreateModel } from '../util/mongoose-utils';
  9. const logger = loggerFactory('growi:models:bookmark');
  10. export interface BookmarkDocument extends IBookmark, Document {
  11. _id: Types.ObjectId;
  12. page: Types.ObjectId;
  13. user: Types.ObjectId;
  14. createdAt: Date;
  15. }
  16. export interface BookmarkModel extends Model<BookmarkDocument> {
  17. countByPageId(pageId: Types.ObjectId | string): Promise<number>;
  18. getPageIdToCountMap(
  19. pageIds: Types.ObjectId[],
  20. ): Promise<{ [key: string]: number }>;
  21. findByPageIdAndUserId(
  22. pageId: Types.ObjectId | string,
  23. userId: Types.ObjectId | string,
  24. ): Promise<BookmarkDocument | null>;
  25. add(
  26. page: Types.ObjectId | string,
  27. user: Types.ObjectId | string,
  28. ): Promise<BookmarkDocument>;
  29. removeBookmarksByPageId(
  30. pageId: Types.ObjectId | string,
  31. ): Promise<{ deletedCount: number }>;
  32. removeBookmark(
  33. pageId: Types.ObjectId | string,
  34. user: Types.ObjectId | string,
  35. ): Promise<BookmarkDocument | null>;
  36. }
  37. const factory = (crowi: Crowi) => {
  38. const bookmarkEvent = crowi.events.bookmark;
  39. const bookmarkSchema = new Schema<BookmarkDocument, BookmarkModel>(
  40. {
  41. page: { type: Schema.Types.ObjectId, ref: 'Page', index: true },
  42. user: { type: Schema.Types.ObjectId, ref: 'User', index: true },
  43. },
  44. {
  45. timestamps: { createdAt: true, updatedAt: false },
  46. },
  47. );
  48. bookmarkSchema.index({ page: 1, user: 1 }, { unique: true });
  49. bookmarkSchema.plugin(mongoosePaginate);
  50. bookmarkSchema.plugin(uniqueValidator);
  51. bookmarkSchema.statics.countByPageId = async function (
  52. pageId: Types.ObjectId | string,
  53. ): Promise<number> {
  54. return await this.countDocuments({ page: pageId });
  55. };
  56. /**
  57. * @return {object} key: page._id, value: bookmark count
  58. */
  59. bookmarkSchema.statics.getPageIdToCountMap = async function (
  60. pageIds: Types.ObjectId[],
  61. ): Promise<{ [key: string]: number }> {
  62. const results = await this.aggregate()
  63. .match({ page: { $in: pageIds } })
  64. .group({ _id: '$page', count: { $sum: 1 } });
  65. // convert to map
  66. const idToCountMap: { [key: string]: number } = {};
  67. results.forEach((result) => {
  68. idToCountMap[result._id] = result.count;
  69. });
  70. return idToCountMap;
  71. };
  72. // bookmark チェック用
  73. bookmarkSchema.statics.findByPageIdAndUserId = async function (
  74. pageId: Types.ObjectId | string,
  75. userId: Types.ObjectId | string,
  76. ): Promise<BookmarkDocument | null> {
  77. return await this.findOne({ page: pageId, user: userId });
  78. };
  79. bookmarkSchema.statics.add = async function (
  80. page: Types.ObjectId | string,
  81. user: Types.ObjectId | string,
  82. ): Promise<BookmarkDocument> {
  83. const newBookmark = new this({ page, user });
  84. try {
  85. const bookmark = await newBookmark.save();
  86. bookmarkEvent.emit('create', page);
  87. return bookmark;
  88. } catch (err: any) {
  89. if (err.code === 11000) {
  90. // duplicate key (dummy response of new object)
  91. return newBookmark;
  92. }
  93. logger.debug('Bookmark.save failed', err);
  94. throw err;
  95. }
  96. };
  97. /**
  98. * Remove bookmark
  99. * used only when removing the page
  100. * @param {string} pageId
  101. */
  102. bookmarkSchema.statics.removeBookmarksByPageId = async function (
  103. pageId: Types.ObjectId | string,
  104. ): Promise<{ deletedCount: number }> {
  105. try {
  106. const result = await this.deleteMany({ page: pageId });
  107. bookmarkEvent.emit('delete', pageId);
  108. return { deletedCount: result.deletedCount ?? 0 };
  109. } catch (err) {
  110. logger.debug('Bookmark.remove failed (removeBookmarkByPage)', err);
  111. throw err;
  112. }
  113. };
  114. bookmarkSchema.statics.removeBookmark = async function (
  115. pageId: Types.ObjectId | string,
  116. user: Types.ObjectId | string,
  117. ): Promise<BookmarkDocument | null> {
  118. try {
  119. const data = await this.findOneAndDelete({ page: pageId, user });
  120. bookmarkEvent.emit('delete', pageId);
  121. return data;
  122. } catch (err) {
  123. logger.debug('Bookmark.findOneAndRemove failed', err);
  124. throw err;
  125. }
  126. };
  127. return getOrCreateModel<BookmarkDocument, BookmarkModel>(
  128. 'Bookmark',
  129. bookmarkSchema,
  130. );
  131. };
  132. export default factory;