external-account.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // disable no-return-await for model functions
  2. /* eslint-disable no-return-await */
  3. import type { IUser } from '@growi/core/dist/interfaces';
  4. import { type IExternalAccount, type IExternalAccountHasId, type IUserHasId } from '@growi/core/dist/interfaces';
  5. import type { Model, Document, HydratedDocument } from 'mongoose';
  6. import mongoose, { Schema } from 'mongoose';
  7. import mongoosePaginate from 'mongoose-paginate-v2';
  8. import uniqueValidator from 'mongoose-unique-validator';
  9. import { NullUsernameToBeRegisteredError } from '~/server/models/errors';
  10. import loggerFactory from '~/utils/logger';
  11. import { getOrCreateModel } from '../util/mongoose-utils';
  12. const logger = loggerFactory('growi:models:external-account');
  13. export interface ExternalAccountDocument extends IExternalAccount, Document {}
  14. export interface ExternalAccountModel extends Model<ExternalAccountDocument> {
  15. [x:string]: any, // for old methods
  16. }
  17. const schema = new Schema<ExternalAccountDocument, ExternalAccountModel>({
  18. providerType: { type: String, required: true },
  19. accountId: { type: String, required: true },
  20. user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
  21. }, {
  22. timestamps: { createdAt: true, updatedAt: false },
  23. });
  24. // compound index
  25. schema.index({ providerType: 1, accountId: 1 }, { unique: true });
  26. // apply plugins
  27. schema.plugin(mongoosePaginate);
  28. schema.plugin(uniqueValidator);
  29. /**
  30. * limit items num for pagination
  31. */
  32. const DEFAULT_LIMIT = 50;
  33. /**
  34. * The Exception class thrown when User.username is duplicated when creating user
  35. *
  36. * @class DuplicatedUsernameException
  37. */
  38. class DuplicatedUsernameException {
  39. name: string;
  40. message: string;
  41. user: IUserHasId;
  42. constructor(message, user) {
  43. this.name = this.constructor.name;
  44. this.message = message;
  45. this.user = user;
  46. }
  47. }
  48. /**
  49. * find an account or register if not found
  50. */
  51. schema.statics.findOrRegister = function(
  52. isSameUsernameTreatedAsIdenticalUser: boolean,
  53. isSameEmailTreatedAsIdenticalUser: boolean,
  54. providerType: string,
  55. accountId: string,
  56. usernameToBeRegistered?: string,
  57. nameToBeRegistered?: string,
  58. mailToBeRegistered?: string,
  59. ): Promise<IExternalAccountHasId> {
  60. return this.findOne({ providerType, accountId })
  61. .then((account) => {
  62. // ExternalAccount is found
  63. if (account != null) {
  64. logger.debug(`ExternalAccount '${accountId}' is found `, account);
  65. return account;
  66. }
  67. if (usernameToBeRegistered == null) {
  68. throw new NullUsernameToBeRegisteredError('username_should_not_be_null');
  69. }
  70. const User = mongoose.model<HydratedDocument<IUser>, Model<IUser> & { createUser, STATUS_ACTIVE }>('User');
  71. let promise = User.findOne({ username: usernameToBeRegistered }).exec();
  72. if (isSameUsernameTreatedAsIdenticalUser && isSameEmailTreatedAsIdenticalUser) {
  73. promise = promise
  74. .then((user) => {
  75. if (user == null) { return User.findOne({ email: mailToBeRegistered }) }
  76. return user;
  77. });
  78. }
  79. else if (isSameEmailTreatedAsIdenticalUser) {
  80. promise = User.findOne({ email: mailToBeRegistered }).exec();
  81. }
  82. return promise
  83. .then((user) => {
  84. // when the User that have the same `username` exists
  85. if (user != null) {
  86. throw new DuplicatedUsernameException(`User '${usernameToBeRegistered}' already exists`, user);
  87. }
  88. if (nameToBeRegistered == null) {
  89. // eslint-disable-next-line no-param-reassign
  90. nameToBeRegistered = '';
  91. }
  92. // create a new User with STATUS_ACTIVE
  93. logger.debug(`ExternalAccount '${accountId}' is not found, it is going to be registered.`);
  94. return User.createUser(nameToBeRegistered, usernameToBeRegistered, mailToBeRegistered, undefined, undefined, User.STATUS_ACTIVE);
  95. })
  96. .then((newUser) => {
  97. return this.associate(providerType, accountId, newUser);
  98. });
  99. });
  100. };
  101. /**
  102. * Create ExternalAccount document and associate to existing User
  103. */
  104. schema.statics.associate = function(providerType: string, accountId: string, user: IUserHasId) {
  105. return this.create({ providerType, accountId, user: user._id });
  106. };
  107. /**
  108. * find all entities with pagination
  109. *
  110. * @see https://github.com/edwardhotchkiss/mongoose-paginate
  111. *
  112. * @static
  113. * @param {any} opts mongoose-paginate options object
  114. * @returns {Promise<any>} mongoose-paginate result object
  115. * @memberof ExternalAccount
  116. */
  117. schema.statics.findAllWithPagination = function(opts) {
  118. const query = {};
  119. const options = Object.assign({ populate: 'user' }, opts);
  120. if (options.sort == null) {
  121. options.sort = { accountId: 1, createdAt: 1 };
  122. }
  123. if (options.limit == null) {
  124. options.limit = DEFAULT_LIMIT;
  125. }
  126. return this.paginate(query, options);
  127. };
  128. export default getOrCreateModel<ExternalAccountDocument, ExternalAccountModel>('ExternalAccount', schema);