src/modules/article/article.model.ts
Properties |
|
categories |
Type : Ref<Category>[]
|
Decorators :
@ArrayUnique()
|
Defined in src/modules/article/article.model.ts:157
|
content |
Type : string
|
Decorators :
@IsString()
|
Defined in src/modules/article/article.model.ts:114
|
Optional created_at |
Type : Date
|
Decorators :
@prop({default: undefined, index: true, immutable: true})
|
Defined in src/modules/article/article.model.ts:188
|
description |
Type : string
|
Decorators :
@IsString()
|
Defined in src/modules/article/article.model.ts:118
|
disabled_comments |
Type : boolean
|
Decorators :
@IsBoolean()
|
Defined in src/modules/article/article.model.ts:182
|
extends |
Type : KeyValueModel[]
|
Decorators :
@ArrayUnique()
|
Defined in src/modules/article/article.model.ts:196
|
featured |
Type : boolean
|
Decorators :
@IsBoolean()
|
Defined in src/modules/article/article.model.ts:177
|
id |
Type : number
|
Decorators :
@prop({unique: true})
|
Defined in src/modules/article/article.model.ts:97
|
keywords |
Type : string[]
|
Decorators :
@ArrayUnique()
|
Defined in src/modules/article/article.model.ts:124
|
lang |
Type : Language
|
Decorators :
@IsIn(ARTICLE_LANGUAGES)
|
Defined in src/modules/article/article.model.ts:172
|
meta |
Type : ArticleMeta
|
Decorators :
@prop({_id: false, default: undefined})
|
Defined in src/modules/article/article.model.ts:185
|
origin |
Type : OriginState
|
Decorators :
@IsIn(ARTICLE_ORIGIN_STATES)
|
Defined in src/modules/article/article.model.ts:150
|
public |
Type : PublicState
|
Decorators :
@IsIn(ARTICLE_PUBLIC_STATES)
|
Defined in src/modules/article/article.model.ts:143
|
slug |
Type : string
|
Decorators :
@Matches(/^[a-zA-Z0-9-_]+$/)
|
Defined in src/modules/article/article.model.ts:104
|
state |
Type : PublishState
|
Decorators :
@IsIn(ARTICLE_PUBLISH_STATES)
|
Defined in src/modules/article/article.model.ts:136
|
tags |
Type : Ref<Tag>[]
|
Decorators :
@prop({ref: () => Tag, index: true})
|
Defined in src/modules/article/article.model.ts:162
|
thumbnail |
Type : string | null
|
Decorators :
@IsString()
|
Defined in src/modules/article/article.model.ts:129
|
title |
Type : string
|
Decorators :
@IsString()
|
Defined in src/modules/article/article.model.ts:109
|
Optional updated_at |
Type : Date
|
Decorators :
@prop({default: undefined})
|
Defined in src/modules/article/article.model.ts:191
|
import { AutoIncrementID } from '@typegoose/auto-increment'
import { prop, index, plugin, Ref, modelOptions } from '@typegoose/typegoose'
import {
IsString,
IsBoolean,
IsNotEmpty,
IsArray,
IsOptional,
IsDefined,
IsIn,
IsInt,
MaxLength,
Matches,
ArrayNotEmpty,
ArrayUnique
} from 'class-validator'
import { Language, SortType, PublishState, PublicState, OriginState } from '@app/constants/biz.constant'
import { GENERAL_AUTO_INCREMENT_ID_CONFIG } from '@app/constants/increment.constant'
import { getProviderByTypegooseClass } from '@app/transformers/model.transformer'
import { mongoosePaginate } from '@app/utils/paginate'
import { Category } from '@app/modules/category/category.model'
import { KeyValueModel } from '@app/models/key-value.model'
import { Tag } from '@app/modules/tag/tag.model'
export const ARTICLE_LANGUAGES = [Language.English, Language.Chinese, Language.Mixed] as const
export const ARTICLE_PUBLISH_STATES = [PublishState.Draft, PublishState.Published, PublishState.Recycle] as const
export const ARTICLE_PUBLIC_STATES = [PublicState.Public, PublicState.Secret, PublicState.Reserve] as const
export const ARTICLE_ORIGIN_STATES = [OriginState.Original, OriginState.Reprint, OriginState.Hybrid] as const
export const ARTICLE_FULL_QUERY_REF_POPULATE = ['categories', 'tags']
export const ARTICLE_LIST_QUERY_PROJECTION = { content: false }
export const ARTICLE_LIST_QUERY_GUEST_FILTER = Object.freeze({
state: PublishState.Published,
public: PublicState.Public
})
export const ARTICLE_HOTTEST_SORT_PARAMS = Object.freeze({
'meta.comments': SortType.Desc,
'meta.likes': SortType.Desc
})
const ARTICLE_DEFAULT_META: ArticleMeta = Object.freeze({
likes: 0,
views: 0,
comments: 0
})
export class ArticleMeta {
@IsInt()
@prop({ default: 0 })
likes: number
@IsInt()
@prop({ default: 0 })
views: number
// MARK: keep comments field manual
// 1. `.sort()` can't by other model schema
// https://stackoverflow.com/questions/66174791/how-to-access-a-different-schema-in-a-virtual-method
// 2. `virtual` can't support publicOnly params and can't access other schema
@IsInt()
@prop({ default: 0 })
comments: number
}
@plugin(mongoosePaginate)
@plugin(AutoIncrementID, GENERAL_AUTO_INCREMENT_ID_CONFIG)
@modelOptions({
schemaOptions: {
versionKey: false,
toObject: { getters: true },
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
}
})
@index(
{ title: 'text', content: 'text', description: 'text' },
{
name: 'SearchIndex',
weights: {
title: 10,
description: 18,
content: 3
}
}
)
export class Article {
@prop({ unique: true })
id: number
@Matches(/^[a-zA-Z0-9-_]+$/)
@MaxLength(50)
@IsString()
@IsOptional()
@prop({ default: null, validate: /^[a-zA-Z0-9-_]+$/, index: true })
slug: string
@IsString()
@IsNotEmpty({ message: 'title?' })
@prop({ required: true, validate: /\S+/, text: true })
title: string
@IsString()
@IsNotEmpty({ message: 'content?' })
@prop({ required: true, validate: /\S+/, text: true })
content: string
@IsString()
@prop({ default: '', text: true })
description: string
@ArrayUnique()
@IsArray()
@IsDefined()
@prop({ default: [], type: () => [String] })
keywords: string[]
@IsString()
@IsOptional()
@prop({ type: String, default: null })
thumbnail: string | null
// publish state
@IsIn(ARTICLE_PUBLISH_STATES)
@IsInt()
@IsDefined()
@prop({ enum: PublishState, default: PublishState.Published, index: true })
state: PublishState
// public state
@IsIn(ARTICLE_PUBLIC_STATES)
@IsInt()
@IsDefined()
@prop({ enum: PublicState, default: PublicState.Public, index: true })
public: PublicState
// origin state
@IsIn(ARTICLE_ORIGIN_STATES)
@IsInt()
@IsDefined()
@prop({ enum: OriginState, default: OriginState.Original, index: true })
origin: OriginState
// category
@ArrayUnique()
@ArrayNotEmpty()
@IsArray()
@prop({ ref: () => Category, required: true, index: true })
categories: Ref<Category>[]
// tag
// https://typegoose.github.io/typegoose/docs/api/virtuals#virtual-populate
@prop({ ref: () => Tag, index: true })
tags: Ref<Tag>[]
// language
// MARK: can't use 'language' field
// https://docs.mongodb.com/manual/tutorial/specify-language-for-text-index/
// https://docs.mongodb.com/manual/reference/text-search-languages/#std-label-text-search-languages
@IsIn(ARTICLE_LANGUAGES)
@IsString()
@IsDefined()
@prop({ default: Language.Chinese, index: true })
lang: Language
// featured
@IsBoolean()
@prop({ default: false, index: true })
featured: boolean
// disabled comments
@IsBoolean()
@prop({ default: false })
disabled_comments: boolean
@prop({ _id: false, default: { ...ARTICLE_DEFAULT_META } })
meta: ArticleMeta
@prop({ default: Date.now, index: true, immutable: true })
created_at?: Date
@prop({ default: Date.now })
updated_at?: Date
@ArrayUnique()
@IsArray()
@prop({ _id: false, default: [], type: () => [KeyValueModel] })
extends: KeyValueModel[]
}
export const ArticleProvider = getProviderByTypegooseClass(Article)