-
-
Notifications
You must be signed in to change notification settings - Fork 7
Attempts to integrate the mongoose-fuzzy-searching library #670
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| declare module 'mongoose-fuzzy-searching' { | ||
| import { Document, DocumentQuery, HookAsyncCallback, HookSyncCallback, Model, Schema } from 'mongoose' | ||
|
|
||
| export type FuzzyFieldStringOptions<T> = (keyof T & string)[]; | ||
|
|
||
| export interface FuzzyFieldOptions<T> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs and defaults from this come from the package's docs. |
||
| /** | ||
| * Collection key name. If unspecified, defualts to **null**. | ||
| */ | ||
| name?: string | null; | ||
|
|
||
| /** | ||
| * N-grams min size. If unspecified, defaults to **2**. | ||
| */ | ||
| minSize?: number; | ||
|
|
||
| /** | ||
| * Denotes the significance of the field relative to the other indexed fields in terms of the text search score. | ||
| * If unspecified, defaults to **1**. | ||
| */ | ||
| weight?: number; | ||
|
|
||
| /** | ||
| * Only return ngrams from start of word. (It gives more precise results). | ||
| * If unspecified, defaults to **false**. | ||
| */ | ||
| prefixOnly?: boolean; | ||
|
|
||
| /** | ||
| * Remove special characters from N-grams. | ||
| * If unspecified, defaults to **true**. | ||
| */ | ||
| escapeSpecialCharacters?: boolean; | ||
|
|
||
| /** | ||
| * Defines which attributes on this object to be used for fuzzy searching. | ||
| * If unspecified, defaults to **null**. | ||
| */ | ||
| keys: FuzzyFieldStringOptions<T>; | ||
| } | ||
|
|
||
| export interface MongooseFuzzyOptions<T> { | ||
| /** | ||
| * Defines the fields to fuzzy search. Can either be an array of strings | ||
| * (in which case defaults will be used), or an array of objects, | ||
| * that define the options for each field. | ||
| */ | ||
| fields: FuzzyFieldStringOptions<T> | FuzzyFieldOptions<T>; | ||
| middlewares?: { | ||
| preSave?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| preInsertMany?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| preUpdate?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| preUpdateOne?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| preFindOneAndUpdate?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| preUpdateMany?: HookSyncCallback<T> | HookAsyncCallback<T>; | ||
| } | ||
| } | ||
|
|
||
| export interface MongooseFuzzyModel<T extends Document, QueryHelpers = Record<string, unknown>> | ||
| extends Model<T, QueryHelpers> { | ||
| fuzzySearch( | ||
| search: string, | ||
| callBack?: (err: any, data: Model<T, QueryHelpers>[]) => void | ||
| ): DocumentQuery<T[], T, QueryHelpers> | ||
| } | ||
|
|
||
| export default function registerFuzzySearch<T>(schema: Schema<T>, options: MongooseFuzzyOptions<T>): void | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this needs to be defined as a |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,12 @@ import { ContentDocument, ContentSchema } from './content.schema'; | |
| import { RatingsSchema } from './ratings.schema'; | ||
| import { ReadingHistorySchema } from './reading-history.schema'; | ||
| import { SectionsDocument, SectionsSchema } from './sections.schema'; | ||
| import { TagsDocument, TagsSchema } from './tags.schema'; | ||
| import { TagsSchema } from './tags.schema'; | ||
| import * as MongooseAutopopulate from 'mongoose-autopopulate'; | ||
| import * as MongoosePaginate from 'mongoose-paginate-v2'; | ||
| import { countWords, stripTags } from 'voca'; | ||
| import { MongooseFuzzyOptions } from 'mongoose-fuzzy-searching'; | ||
| import registerFuzzySearch from 'mongoose-fuzzy-searching'; | ||
|
|
||
| //#region ---EXPORTS--- | ||
|
|
||
|
|
@@ -35,19 +37,24 @@ export async function setupContentCollection() { | |
| // making a text index on the title field for search | ||
| schema.index({ title: 'text' }); | ||
|
|
||
| schema.pre<ContentDocument>('save', async function (next: HookNextFunction) { | ||
| this.set('title', sanitizeHtml(this.title, sanitizeOptions)); | ||
| this.set('body', sanitizeHtml(this.body, sanitizeOptions)); | ||
|
|
||
| // this will only trigger if any creation or editing functions has modified the `desc` field, | ||
| // otherwise we'll leave it alone | ||
| if (this.isModified('desc')) { | ||
| this.set('desc', sanitizeHtml(this.desc, sanitizeOptions)); | ||
| schema.plugin<MongooseFuzzyOptions<ContentDocument>>(registerFuzzySearch,{ | ||
| fields: ['title'], | ||
| // Middlewares must be defined inside the fuzzy-search plugin, otherwise it will override them. | ||
| middlewares: { | ||
| preSave: async function (next: HookNextFunction) { | ||
| this.set('title', sanitizeHtml(this.title, sanitizeOptions)); | ||
| this.set('body', sanitizeHtml(this.body, sanitizeOptions)); | ||
|
|
||
| // this will only trigger if any creation or editing functions has modified the `desc` field, | ||
| // otherwise we'll leave it alone | ||
| if (this.isModified('desc')) { | ||
| this.set('desc', sanitizeHtml(this.desc, sanitizeOptions)); | ||
| } | ||
|
|
||
| return next(); | ||
|
Comment on lines
+44
to
+54
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the mongoose-fuzzy-searching docs, it overrides any |
||
| } | ||
| } | ||
|
|
||
| return next(); | ||
| }); | ||
|
|
||
| schema.plugin(MongooseAutopopulate); | ||
| schema.plugin(MongoosePaginate); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import { RatingOption } from '@dragonfish/shared/models/reading-history'; | |
| import { JwtPayload } from '@dragonfish/shared/models/auth'; | ||
| import { ContentFilter, ContentKind, ContentRating, PubStatus } from '@dragonfish/shared/models/content'; | ||
| import { Pseudonym } from '@dragonfish/shared/models/accounts'; | ||
| import { MongooseFuzzyModel } from 'mongoose-fuzzy-searching'; | ||
|
|
||
| /** | ||
| * ## Content Group Store | ||
|
|
@@ -18,6 +19,7 @@ export class ContentGroupStore { | |
| readonly NEWEST_FIRST = -1 | ||
| constructor( | ||
| @InjectModel('Content') private readonly content: PaginateModel<ContentDocument>, | ||
| @InjectModel('Content') private readonly fuzzySearchableContent: MongooseFuzzyModel<ContentDocument>, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was kind of surprised this worked! But it did. Nice and easy. |
||
| @InjectModel('Sections') private readonly sections: Model<SectionsDocument>, | ||
| @InjectModel('Ratings') private readonly ratings: Model<RatingsDocument>, | ||
| @InjectModel('ReadingHistory') private readonly history: Model<ReadingHistoryDocument>, | ||
|
|
@@ -201,7 +203,8 @@ export class ContentGroupStore { | |
| kind: { $in: kinds }, | ||
| }; | ||
| await ContentGroupStore.determineContentFilter(paginateQuery, filter); | ||
| return await this.content.paginate(paginateQuery, paginateOptions); | ||
| const fuzzySearchedContent = await this.fuzzySearchableContent.fuzzySearch(query); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we're not actually doing anything with this variable. |
||
| return await this.content.paginate(paginateQuery, paginateOptions); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -211,7 +214,7 @@ export class ContentGroupStore { | |
| * @param pageNum The page of results to retrieve. | ||
| * @param maxPerPage The maximum number of results per page. | ||
| * @param filter The content filter to apply to returned results. | ||
| * @returns | ||
| * @returns | ||
| */ | ||
| public async getContentByFandomTag( | ||
| tagId: string, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,8 @@ | |
| "outDir": "../../../dist/out-tsc", | ||
| "declaration": true, | ||
| "types": ["node"], | ||
| "target": "es6" | ||
| "target": "es6", | ||
| "typeRoots": ["../../../node_modules/@types", "src/customTypes"] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we have some custom types, we need to tell the compiler where to find them. Since we do that, we have to tell it where to find all of them, so the node_modules types go in here too. We could import the |
||
| }, | ||
| "exclude": ["**/*.spec.ts"], | ||
| "include": ["**/*.ts"] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This basically means "the only fields allowed on this type are the fields already defined on
T, as strings".