Jelajahi Sumber

re-implement serializers

Yuki Takei 1 tahun lalu
induk
melakukan
3c763d8668

+ 4 - 0
packages/core/package.json

@@ -30,6 +30,10 @@
       "import": "./dist/models/index.js",
       "require": "./dist/models/index.cjs"
     },
+    "./dist/models/serializers": {
+      "import": "./dist/models/serializers/index.js",
+      "require": "./dist/models/serializers/index.cjs"
+    },
     "./dist/remark-plugins": {
       "import": "./dist/remark-plugins/index.js",
       "require": "./dist/remark-plugins/index.cjs"

+ 11 - 1
packages/core/src/interfaces/common.ts

@@ -2,9 +2,10 @@
  * Common types and interfaces
  */
 
-
 import type { Types } from 'mongoose';
 
+import { isValidObjectId } from '../utils/objectid-utils';
+
 type ObjectId = Types.ObjectId;
 
 // Foreign key field
@@ -12,6 +13,15 @@ export type Ref<T> = string | ObjectId | T & { _id: string | ObjectId };
 
 export type Nullable<T> = T | null | undefined;
 
+export const isRef = <T>(obj: unknown): obj is Ref<T> => {
+  return obj != null
+    && (
+      (typeof obj === 'string' && isValidObjectId(obj))
+        || (typeof obj === 'object' && '_bsontype' in obj && obj._bsontype === 'ObjectID')
+        || (typeof obj === 'object' && '_id' in obj)
+    );
+};
+
 export const isPopulated = <T>(ref: Ref<T>): ref is T & { _id: string | ObjectId } => {
   return ref != null
     && typeof ref !== 'string'

+ 2 - 0
packages/core/src/models/serializers/index.ts

@@ -0,0 +1,2 @@
+export * from './user-serializer';
+export * from './attachment-serializer';

+ 26 - 24
packages/core/src/models/serializers/user-serializer.ts

@@ -1,35 +1,37 @@
-const mongoose = require('mongoose');
+import { Document } from 'mongoose';
 
+import { isPopulated, isRef, type Ref } from '../../interfaces/common';
+import type { IUser } from '../../interfaces/user';
 
-export function omitInsecureAttributes(user) {
-  // omit password
-  delete user.password;
-  // omit apiToken
-  delete user.apiToken;
+export type IUserSerializedSecurely<U extends IUser> = Omit<U, 'password' | 'apiToken' | 'email'> & { email?: string };
 
-  // omit email
-  if (!user.isEmailPublished) {
-    delete user.email;
-  }
-  return user;
-}
+export const omitInsecureAttributes = <U extends IUser>(user: U): IUserSerializedSecurely<U> => {
 
-export function serializeUserSecurely(user) {
-  const User = mongoose.model('User');
+  const leanDoc = (user instanceof Document)
+    ? user.toObject<U>()
+    : user;
 
-  // return when it is not a user object
-  if (user == null || !(user instanceof User)) {
-    return user;
-  }
+  const {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    password, apiToken, email, ...rest
+  } = leanDoc;
 
-  let serialized = user;
+  const secureUser: IUserSerializedSecurely<U> = rest;
 
-  // invoke toObject if page is a model instance
-  if (user.toObject != null) {
-    serialized = user.toObject();
+  // omit email
+  if (secureUser.isEmailPublished) {
+    secureUser.email = email;
   }
 
-  omitInsecureAttributes(serialized);
+  return secureUser;
+};
+
+export function serializeUserSecurely<U extends IUser>(user?: U): IUserSerializedSecurely<U>;
+export function serializeUserSecurely<U extends IUser>(user?: Ref<U>): Ref<IUserSerializedSecurely<U>>;
+export function serializeUserSecurely<U extends IUser>(user?: U | Ref<U>): undefined | IUserSerializedSecurely<U> | Ref<IUserSerializedSecurely<U>> {
+  if (user == null) return user;
+
+  if (isRef(user) && !isPopulated(user)) return user;
 
-  return serialized;
+  return omitInsecureAttributes(user);
 }