Skip to content

Commit 14ed55b

Browse files
committed
feat(content): introduce content preference handling for notes and pages
- Added `zPrefer` schema to support content preference in note and page queries. - Implemented `applyContentPreference` utility function to conditionally format content based on user preference. - Updated note and page controllers to utilize the new preference handling in response data. - Enhanced API client controllers to accept and forward content preference options. Signed-off-by: Innei <tukon479@gmail.com>
1 parent 534b0f7 commit 14ed55b

File tree

17 files changed

+122
-61
lines changed

17 files changed

+122
-61
lines changed

apps/core/src/common/zod/custom.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const zRefTypeTransform = z.preprocess((val) => {
5353
return mapping[val.toLowerCase()] || val
5454
}, z.string().optional())
5555

56+
export const zPrefer = z.enum(['lexical']).optional()
57+
5658
export const zLang = z
5759
.preprocess(
5860
(val) =>

apps/core/src/common/zod/index.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
export { z } from 'zod'
2-
export { createZodDto } from 'nestjs-zod'
3-
1+
export {
2+
zBooleanOrString,
3+
zEmail,
4+
zLang,
5+
zMaxLengthString,
6+
zPinDate,
7+
zPrefer,
8+
zRefTypeTransform,
9+
zSlug,
10+
zTransformBoolean,
11+
zTransformEmptyNull,
12+
zUrl,
13+
} from './custom'
414
export {
515
zAllowedUrl,
616
zArrayUnique,
@@ -23,21 +33,9 @@ export {
2333
zStrictUrl,
2434
zUniqueStringArray,
2535
} from './primitives'
26-
27-
export {
28-
zBooleanOrString,
29-
zEmail,
30-
zLang,
31-
zMaxLengthString,
32-
zPinDate,
33-
zRefTypeTransform,
34-
zSlug,
35-
zTransformBoolean,
36-
zTransformEmptyNull,
37-
zUrl,
38-
} from './custom'
39-
4036
export {
4137
ExtendedZodValidationPipe,
4238
extendedZodValidationPipeInstance,
4339
} from './validation.pipe'
40+
export { createZodDto } from 'nestjs-zod'
41+
export { z } from 'zod'

apps/core/src/modules/note/note.controller.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
import { ApiController } from '~/common/decorators/api-controller.decorator'
1212
import { Auth } from '~/common/decorators/auth.decorator'
1313
import { HTTPDecorators, Paginator } from '~/common/decorators/http.decorator'
14-
import { IpLocation } from '~/common/decorators/ip.decorator'
1514
import type { IpRecord } from '~/common/decorators/ip.decorator'
15+
import { IpLocation } from '~/common/decorators/ip.decorator'
1616
import { Lang } from '~/common/decorators/lang.decorator'
1717
import { IsAuthenticated } from '~/common/decorators/role.decorator'
1818
import { BizException } from '~/common/exceptions/biz.exception'
@@ -22,6 +22,7 @@ import { CountingService } from '~/processors/helper/helper.counting.service'
2222
import { TranslationService } from '~/processors/helper/helper.translation.service'
2323
import { MongoIdDto } from '~/shared/dto/id.dto'
2424
import { addYearCondition } from '~/transformers/db-query.transformer'
25+
import { applyContentPreference } from '~/utils/content.util'
2526
import type { QueryFilter } from 'mongoose'
2627
import { NoteModel } from './note.model'
2728
import {
@@ -76,7 +77,7 @@ export class NoteController {
7677
page,
7778
select: isAuthenticated
7879
? select
79-
: select?.replace(/[+-]?(coordinates|location|password)/g, ''),
80+
: select?.replaceAll(/[+-]?(coordinates|location|password)/g, ''),
8081
sort: sortBy ? { [sortBy]: sortOrder || -1 } : { created: -1 },
8182
})
8283
}
@@ -242,7 +243,7 @@ export class NoteController {
242243
@Lang() lang?: string,
243244
) {
244245
const { nid } = params
245-
const { password, single: isSingle } = query
246+
const { password, single: isSingle, prefer } = query
246247
const condition = isAuthenticated ? {} : { isPublished: true }
247248
const current: NoteModel | null = await this.noteService.model
248249
.findOne({
@@ -292,7 +293,7 @@ export class NoteController {
292293
}
293294

294295
if (isSingle) {
295-
return currentData
296+
return applyContentPreference(currentData, prefer)
296297
}
297298

298299
const select = '_id title nid id created modified'
@@ -320,7 +321,7 @@ export class NoteController {
320321
if (currentData.password) {
321322
currentData.password = '*'
322323
}
323-
return { data: currentData, next, prev }
324+
return { data: applyContentPreference(currentData, prefer), next, prev }
324325
}
325326

326327
@Get('/topics/:id')

apps/core/src/modules/note/note.schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
zLang,
55
zMongoId,
66
zNonEmptyString,
7+
zPrefer,
78
zTransformEmptyNull,
89
} from '~/common/zod'
910
import { PagerSchema } from '~/shared/dto/pager.dto'
@@ -78,6 +79,7 @@ export const NotePasswordQuerySchema = z.object({
7879
password: zNonEmptyString.optional(),
7980
single: zCoerceBoolean.optional(),
8081
lang: zLang,
82+
prefer: zPrefer,
8183
})
8284

8385
export class NotePasswordQueryDto extends createZodDto(

apps/core/src/modules/page/page.controller.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ import {
2121
} from '~/processors/helper/helper.translation.service'
2222
import { MongoIdDto } from '~/shared/dto/id.dto'
2323
import { PagerDto } from '~/shared/dto/pager.dto'
24+
import { applyContentPreference } from '~/utils/content.util'
2425
import { PageModel } from './page.model'
25-
import { PageDto, PageReorderDto, PartialPageDto } from './page.schema'
26+
import {
27+
PageDetailQueryDto,
28+
PageDto,
29+
PageReorderDto,
30+
PartialPageDto,
31+
} from './page.schema'
2632
import { PageService } from './page.service'
2733

2834
@ApiController('pages')
@@ -110,7 +116,11 @@ export class PageController {
110116
}
111117

112118
@Get('/slug/:slug')
113-
async getPageBySlug(@Param('slug') slug: string, @Lang() lang?: string) {
119+
async getPageBySlug(
120+
@Param('slug') slug: string,
121+
@Query() query: PageDetailQueryDto,
122+
@Lang() lang?: string,
123+
) {
114124
if (typeof slug !== 'string') {
115125
throw new BizException(ErrorCodeEnum.InvalidSlug)
116126
}
@@ -133,14 +143,17 @@ export class PageController {
133143
},
134144
})
135145

136-
return {
137-
...page,
138-
title: translationResult.title,
139-
text: translationResult.text,
140-
isTranslated: translationResult.isTranslated,
141-
translationMeta: translationResult.translationMeta,
142-
availableTranslations: translationResult.availableTranslations,
143-
}
146+
return applyContentPreference(
147+
{
148+
...page,
149+
title: translationResult.title,
150+
text: translationResult.text,
151+
isTranslated: translationResult.isTranslated,
152+
translationMeta: translationResult.translationMeta,
153+
availableTranslations: translationResult.availableTranslations,
154+
},
155+
query.prefer,
156+
)
144157
}
145158

146159
@Post('/')

apps/core/src/modules/page/page.schema.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { zCoerceInt, zMongoId, zNonEmptyString } from '~/common/zod'
1+
import { zCoerceInt, zMongoId, zNonEmptyString, zPrefer } from '~/common/zod'
22
import { WriteBaseSchema } from '~/shared/schema'
33
import { ImageSchema } from '~/shared/schema/image.schema'
44
import { createZodDto } from 'nestjs-zod'
@@ -46,6 +46,15 @@ export const PageReorderSchema = z.object({
4646

4747
export class PageReorderDto extends createZodDto(PageReorderSchema) {}
4848

49+
/**
50+
* Page detail query schema
51+
*/
52+
export const PageDetailQuerySchema = z.object({
53+
prefer: zPrefer,
54+
})
55+
56+
export class PageDetailQueryDto extends createZodDto(PageDetailQuerySchema) {}
57+
4958
// Type exports
5059
export type PageInput = z.infer<typeof PageSchema>
5160
export type PartialPageInput = z.infer<typeof PartialPageSchema>

apps/core/src/modules/post/post.controller.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
import { ApiController } from '~/common/decorators/api-controller.decorator'
1212
import { Auth } from '~/common/decorators/auth.decorator'
1313
import { HTTPDecorators, Paginator } from '~/common/decorators/http.decorator'
14-
import { IpLocation } from '~/common/decorators/ip.decorator'
1514
import type { IpRecord } from '~/common/decorators/ip.decorator'
15+
import { IpLocation } from '~/common/decorators/ip.decorator'
1616
import { Lang } from '~/common/decorators/lang.decorator'
1717
import { IsAuthenticated } from '~/common/decorators/role.decorator'
1818
import { CannotFindException } from '~/common/exceptions/cant-find.exception'
@@ -23,6 +23,7 @@ import {
2323
} from '~/processors/helper/helper.translation.service'
2424
import { MongoIdDto } from '~/shared/dto/id.dto'
2525
import { addYearCondition } from '~/transformers/db-query.transformer'
26+
import { applyContentPreference } from '~/utils/content.util'
2627
import type { PipelineStage } from 'mongoose'
2728
import type { CategoryModel } from '../category/category.model'
2829
import { PostModel } from './post.model'
@@ -276,7 +277,7 @@ export class PostController {
276277
@Get('/:category/:slug')
277278
async getByCateAndSlug(
278279
@Param() params: CategoryAndSlugDto,
279-
@Query() _: PostDetailQueryDto,
280+
@Query() query: PostDetailQueryDto,
280281
@IpLocation() { ip }: IpRecord,
281282
@IsAuthenticated() isAuthenticated?: boolean,
282283
@Lang() lang?: string,
@@ -314,17 +315,20 @@ export class PostController {
314315
},
315316
})
316317

317-
return {
318-
...baseData,
319-
title: translationResult.title,
320-
text: translationResult.text,
321-
summary: translationResult.summary,
322-
tags: translationResult.tags,
323-
isTranslated: translationResult.isTranslated,
324-
translationMeta: translationResult.translationMeta,
325-
availableTranslations: translationResult.availableTranslations,
326-
liked,
327-
}
318+
return applyContentPreference(
319+
{
320+
...baseData,
321+
title: translationResult.title,
322+
text: translationResult.text,
323+
summary: translationResult.summary,
324+
tags: translationResult.tags,
325+
isTranslated: translationResult.isTranslated,
326+
translationMeta: translationResult.translationMeta,
327+
availableTranslations: translationResult.availableTranslations,
328+
liked,
329+
},
330+
query.prefer,
331+
)
328332
}
329333

330334
@Post('/')

apps/core/src/modules/post/post.schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
zMongoId,
66
zNonEmptyString,
77
zPinDate,
8+
zPrefer,
89
} from '~/common/zod'
910
import { PagerSchema } from '~/shared/dto/pager.dto'
1011
import { WriteBaseSchema } from '~/shared/schema'
@@ -64,6 +65,7 @@ export class CategoryAndSlugDto extends createZodDto(CategoryAndSlugSchema) {}
6465
*/
6566
export const PostDetailQuerySchema = z.object({
6667
lang: zLang,
68+
prefer: zPrefer,
6769
})
6870

6971
export class PostDetailQueryDto extends createZodDto(PostDetailQuerySchema) {}

apps/core/src/utils/content.util.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ export function computeContentHash(
6464
)
6565
}
6666

67+
export function applyContentPreference<
68+
T extends { text?: string; contentFormat?: string; content?: string },
69+
>(doc: T, prefer?: string): T {
70+
if (
71+
prefer === 'lexical' &&
72+
doc.contentFormat === ContentFormat.Lexical &&
73+
doc.content
74+
) {
75+
const { text, ...rest } = doc
76+
return rest as T
77+
}
78+
return doc
79+
}
80+
6781
function traverseLexicalNodes(node: any, visitor: (node: any) => void): void {
6882
if (!node) return
6983
visitor(node)

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export default defineConfig(
5858
'@typescript-eslint/no-unsafe-function-type': 0,
5959
'unicorn/no-array-callback-reference': 0,
6060

61-
61+
"unicorn/no-thenable": 0,
6262
'no-duplicate-imports': 'off',
6363
'unicorn/explicit-length-check': 0,
6464
'unicorn/prefer-top-level-await': 0,

0 commit comments

Comments
 (0)