Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5543aa4
refactor(api): V2 response envelope, snake_case schema, named views
Innei May 20, 2026
5431e1f
fix(api-client): sync dependency specs with lockfile
Innei May 21, 2026
c1c7337
refactor(api): drop no-op ResponseV2 controller decorator
Innei May 21, 2026
a5cf655
fix(ci): update v2 response test expectations
Innei May 21, 2026
a914965
fix(ci): sync remaining v2 test expectations
Innei May 21, 2026
2d658fd
fix(ci): unwrap comment contract list responses
Innei May 21, 2026
8bf61f2
chore(release): bump @mx-space/api-client to v5.0.2-next.0
Innei May 21, 2026
de846e4
fix(api-client): restore HTTPClient augmentations across bundling
Innei May 21, 2026
d96c780
fix(api-client): hoist HTTPClient augmentations from dts chunks to ro…
Innei May 21, 2026
ad8d5d4
fix(api): always emit translation meta on article detail endpoints
Innei May 21, 2026
9b2363f
fix(api-client): legacy adapter rewrite for full V1 wire parity
Innei May 21, 2026
7b0a39c
refactor(api): unify error system on AppErrorCode, replace PagerDto w…
Innei May 21, 2026
ac83cd8
fix(owner): use English literal for mocked owner name
Innei May 21, 2026
0d590c2
fix(api-client): preserve legacy adapter pagination flags
Innei May 21, 2026
7097628
fix(api): smoke-test driven server fixes for v2 API migration
Innei May 21, 2026
7baf566
chore(release): bump @mx-space/api-client to v5.0.2-next.4
Innei May 21, 2026
af70014
chore(release): bump @mx-space/api-client to v5.0.2-next.5
Innei May 21, 2026
8c829b6
fix(api-client): restore V2 pagination aliases via legacy adapter
Innei May 21, 2026
3361683
chore(release): bump @mx-space/api-client to v5.0.2-next.7
Innei May 21, 2026
1453a96
feat(cli): add V2/V3 envelope compatibility layer
Innei May 21, 2026
d997363
refactor(api): camelCase response meta schemas end-to-end
Innei May 21, 2026
77ce8dd
chore(core): translate in-source Chinese strings and comments to English
Innei May 21, 2026
2180f5b
refactor(core): drop redundant snake_case mappings in controllers
Innei May 22, 2026
ed2b8bc
chore(cli): clarify default title in README for create payloads
Innei May 22, 2026
9c240fa
refactor(core): extract collectArticleTranslations helper for list-tr…
Innei May 22, 2026
788cd79
refactor(core): regroup response/ files into common/ type dirs
Innei May 22, 2026
f159c71
fix(api-client): unwrap V3 envelope in legacy adapter when consumer p…
Innei May 22, 2026
e4fe49c
chore(release): bump @mx-space/api-client to v5.0.2-next.8
Innei May 22, 2026
c29d543
docs(spec): add translation in-place overwrite design
Innei May 22, 2026
aa63e31
docs(spec): revise translation in-place overwrite spec per codex review
Innei May 22, 2026
62555ab
docs(spec): apply codex round-2 fixes to translation in-place spec
Innei May 22, 2026
553f91e
refactor(api): slim ArticleTranslationSchema and EntryTranslationSchema
Innei May 22, 2026
c6eb940
refactor(core): slim translation meta builder and add in-place helpers
Innei May 22, 2026
d17bde5
fix(api-client): slim legacy translationMeta + nested-array flatten r…
Innei May 22, 2026
73e7ae6
refactor(core): in-place translation overwrite for note + aggregate
Innei May 22, 2026
3ad65d6
refactor(core): in-place translation overwrite for remaining controllers
Innei May 22, 2026
28278a6
fix(core): wire AiModule and EntryMaps types post-controller-migration
Innei May 22, 2026
133631e
fix(cli): read translation source_lang and translated from meta envelope
Innei May 22, 2026
485601c
refactor(core): fold RawResponse into HTTPDecorators namespace
Innei May 22, 2026
11b3a45
refactor(core): inline await-then-return in controllers
Innei May 22, 2026
72b09e8
refactor(core): inline remaining await-then-return in controllers
Innei May 22, 2026
62d3162
refactor(core): normalize request keys to camelCase before zod
Innei May 22, 2026
2c43fd0
refactor(core): move case normalization pipe to common/pipes
Innei May 22, 2026
c618198
fix(core): accept legacy 1/-1 sortOrder on the wire
Innei May 22, 2026
69b866e
fix(api-client): legacy adapter wraps bare-list and aggregate/timelin…
Innei May 22, 2026
8b92837
feat(api-client): rewrite legacy sortBy=created/modified to V3 *At names
Innei May 22, 2026
fd594e1
feat(core): honor x-skip-translation header in request-context middle…
Innei May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,42 @@ pnpm -C apps/core run test:watch

## API Response Rules

`ResponseInterceptor` auto-wraps responses:
- **Array** → `{ data: [...] }` (always wrapped)
- **Object** → returned directly (no wrapper)
- **@Paginator** → `{ data: [...], pagination: {...} }` (requires `model.paginate()` result)
- **@Bypass** → skips all transformation
Every successful JSON response has the shape `{ data, meta? }`. Every error has the shape `{ error: { code, message, details? } }`.

**Success envelope** — `ResponseInterceptorV2` (global `APP_INTERCEPTOR`) wraps controller return values:
- A bare value `T` → `{ data: T }`
- A value produced via `withMeta(data, meta)` (from `~/common/response/envelope.types`) → emitted as `{ data, meta }`. Detection is by an internal `Symbol`, **not** by the presence of a `data` key — returning a literal `{ data, ... }` will be double-wrapped. CI enforces this via `scripts/check-controller-response-envelope.ts`.
- `undefined` → `204 No Content`

**Error envelope** — `AppExceptionFilter` (global `APP_FILTER`) maps every thrown error:
- `AppException` (and subclasses) → `{ error: { code, message, details? } }` at the exception's HTTP status
- `ZodError` → 400 `VALIDATION_FAILED` with `details.issues`
- Other `HttpException` → `{ error: { code: 'HTTP_ERROR', message } }`
- Unknown errors → 500 `INTERNAL_ERROR`

**Exceptions** — extend `AppException` with a stable `SCREAMING_SNAKE` code:
```ts
throw new BizException(ErrorCodeEnum.PostNotFound) // code: 'PostNotFound', 404
throw new CannotFindException() // code: 'NOT_FOUND', 404
throw new BanInDemoExcpetion() // code: 'DEMO_FORBIDDEN', 403
throw new NoContentCanBeModifiedException() // code: 'NO_CONTENT_MODIFIABLE', 400
```

**Meta** — use `MetaObjectBuilder` for cross-cutting per-request data (pagination, translation, enrichment, interaction). Located in `src/common/response/meta-builder.ts`.

**Named views** — field selection uses `*.views.ts` Zod schemas (e.g. `PostViews.card`, `PostViews.detail`) instead of a `?select=` parameter. Views are parsed at the controller layer.

**Case conversion** — code is camelCase end to end (Drizzle column TS props, Zod DTOs, services). `ResponseInterceptorV2` converts the response `data`/`meta` to snake_case at the wire boundary (`transformResponseCase` in `src/common/response/case-transform.ts`); the wire format stays snake_case. DB column names are unchanged — each Drizzle column keeps its explicit snake_case name string. Never call a manual `snakeCaseKeys`-style helper in a controller.

**`@BypassCaseTransform([paths])`** — opt a field subtree out of snake_case conversion (free-form JSON columns, snippet payloads). Paths root at `data`, dotted segments, `[]` marks an array level (e.g. `'items[].rawPayload'`). Located in `src/common/response/bypass-case-transform.decorator.ts`.

**`@RawResponse`** — opt out of the whole envelope + casing pipeline for non-JSON responses (streams, HTML, RSS, redirects). Located in `src/common/response/raw-response.decorator.ts`.

`JSONTransformInterceptor` converts all keys to **snake_case** (e.g., `createdAt` → `created_at`)
**Writing a new endpoint:**
1. Return `<value>` for a bare envelope, or `withMeta(<value>, new MetaObjectBuilder()...build())` for `{ data, meta }`. Never return an object literal whose top-level keys include `data`.
2. Throw `AppException` subclasses (or `BizException` with an `ErrorCodeEnum` code) for errors.
3. Use `@RawResponse` only if the response is not JSON.
4. Define or reuse a view in `<resource>.views.ts` and parse through it before returning.

## Testing

Expand Down
2 changes: 1 addition & 1 deletion apps/core/src/app.config.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AxiosRequestConfig } from 'axios'

export const PORT = process.env.PORT || 2333
export const API_VERSION = 2
export const API_VERSION = 3

export const CROSS_DOMAIN = {
allowedOrigins: process.env.ALLOWED_ORIGINS
Expand Down
2 changes: 1 addition & 1 deletion apps/core/src/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ if (argv.config) {
applyArgvEnvFallback(argv)

export const PORT = argv.port || 2333
export const API_VERSION = 2
export const API_VERSION = 3

export const DEMO_MODE = argv.demo || false

Expand Down
3 changes: 1 addition & 2 deletions apps/core/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { Auth } from '~/common/decorators/auth.decorator'
import { PKG } from '~/utils/pkg.util'

import { HttpCache } from './common/decorators/cache.decorator'
import { HTTPDecorators } from './common/decorators/http.decorator'
import type { IpRecord } from './common/decorators/ip.decorator'
import { IpLocation } from './common/decorators/ip.decorator'
import { AllowAllCorsInterceptor } from './common/interceptors/allow-all-cors.interceptor'
import { RedisKeys } from './constants/cache.constant'
import { isDev } from './global/env.global'
import { ConfigsService } from './modules/configs/configs.service'
import { RedisService } from './processors/redis/redis.service'
import { getRedisKey } from './utils/redis.util'
Expand All @@ -30,7 +30,6 @@ export class AppController {

@Get('/uptime')
@HttpCache.disable
@HTTPDecorators.Bypass
async getUptime() {
const ts = (process.uptime() * 1000) | 0
return {
Expand Down
18 changes: 4 additions & 14 deletions apps/core/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import { Module } from '@nestjs/common'
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'

import { AppController } from './app.controller'
import { AllExceptionsFilter } from './common/filters/any-exception.filter'
import { RolesGuard } from './common/guards/roles.guard'
import { SpiderGuard } from './common/guards/spider.guard'
import { ExtendThrottlerGuard } from './common/guards/throttler.guard'
import { AnalyzeInterceptor } from './common/interceptors/analyze.interceptor'
import { HttpCacheInterceptor } from './common/interceptors/cache.interceptor'
import { DbQueryInterceptor } from './common/interceptors/db-query.interceptor'
import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor'
import { JSONTransformInterceptor } from './common/interceptors/json-transform.interceptor'
import { ResponseInterceptor } from './common/interceptors/response.interceptor'
import { TranslationEntryInterceptor } from './common/interceptors/translation-entry.interceptor'
import { RequestContextMiddleware } from './common/middlewares/request-context.middleware'
import { AppExceptionFilter } from './common/response/app-exception.filter'
import { ResponseInterceptorV2 } from './common/response/response.interceptor'
import { AppMigrationsModule } from './database/app-migrations/app-migrations.module'
import { AckModule } from './modules/ack/ack.module'
import { ActivityModule } from './modules/activity/activity.module'
Expand Down Expand Up @@ -152,25 +150,17 @@ import { TaskQueueModule } from './processors/task-queue/task-queue.module'

{
provide: APP_INTERCEPTOR,
useClass: JSONTransformInterceptor,
useClass: ResponseInterceptorV2,
},

{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: TranslationEntryInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: IdempotenceInterceptor,
},

{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
useClass: AppExceptionFilter,
},
{
provide: APP_GUARD,
Expand Down
9 changes: 6 additions & 3 deletions apps/core/src/common/controllers/base-task.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Delete, Get, Param, Post, Query } from '@nestjs/common'

import { Auth } from '~/common/decorators/auth.decorator'
import { BizException } from '~/common/exceptions/biz.exception'
import { ErrorCodeEnum } from '~/constants/error-code.constant'
import { AppErrorCode, createAppException } from '~/common/errors'
import type { ScopedTaskService } from '~/processors/task-queue/scoped-task.service'
import { StringIdDto } from '~/shared/dto/id.dto'

import { BaseDeleteTasksQueryDto, BaseGetTasksQueryDto } from './base-task.dto'

export abstract class BaseTaskController {
Expand All @@ -14,7 +15,9 @@ export abstract class BaseTaskController {
async getTask(@Param() params: StringIdDto) {
const task = await this.taskCrudService.getTask(params.id)
if (!task) {
throw new BizException(ErrorCodeEnum.AITaskNotFound)
throw createAppException(AppErrorCode.AI_TASK_NOT_FOUND, {
id: params.id,
})
}
return task
}
Expand Down
3 changes: 0 additions & 3 deletions apps/core/src/common/decorators/http.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import * as SYSTEM from '~/constants/system.constant'

import type { IdempotenceOption } from '../interceptors/idempotence.interceptor'

export const Bypass = SetMetadata(SYSTEM.RESPONSE_PASSTHROUGH_METADATA, true)

export declare interface FileDecoratorProps {
description: string
}
Expand All @@ -19,7 +17,6 @@ export const Idempotence: (options?: IdempotenceOption) => MethodDecorator =
export const SkipLogging = SetMetadata(SYSTEM.SKIP_LOGGING_METADATA, true)

export const HTTPDecorators = {
Bypass,
Idempotence,
SkipLogging,
}
14 changes: 0 additions & 14 deletions apps/core/src/common/decorators/translate-fields.decorator.ts

This file was deleted.

199 changes: 199 additions & 0 deletions apps/core/src/common/errors/app-error-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
export enum AppErrorCode {
// generic
INTERNAL_ERROR = 'INTERNAL_ERROR',
NOT_FOUND = 'NOT_FOUND',
NO_CONTENT_MODIFIABLE = 'NO_CONTENT_MODIFIABLE',
DEMO_FORBIDDEN = 'DEMO_FORBIDDEN',
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
CONTENT_NOT_FOUND = 'CONTENT_NOT_FOUND',
CONTENT_NOT_FOUND_CANT_PROCESS = 'CONTENT_NOT_FOUND_CANT_PROCESS',
MAX_COUNT_LIMIT = 'MAX_COUNT_LIMIT',
MASTER_LOST = 'MASTER_LOST',
CANNOT_GET_IP = 'CANNOT_GET_IP',
ENTRY_NOT_FOUND = 'ENTRY_NOT_FOUND',
REF_MODEL_NOT_FOUND = 'REF_MODEL_NOT_FOUND',

// validation
INVALID_PARAMETER = 'INVALID_PARAMETER',
INVALID_BODY = 'INVALID_BODY',
INVALID_SLUG = 'INVALID_SLUG',
INVALID_NAME = 'INVALID_NAME',
INVALID_REFERENCE = 'INVALID_REFERENCE',
INVALID_ORDER_VALUE = 'INVALID_ORDER_VALUE',
INVALID_SEARCH_TYPE = 'INVALID_SEARCH_TYPE',
INVALID_ROOM_NAME = 'INVALID_ROOM_NAME',
INVALID_SUBSCRIBE_TYPE = 'INVALID_SUBSCRIBE_TYPE',
INVALID_VIEW = 'INVALID_VIEW',
SLUG_NOT_AVAILABLE = 'SLUG_NOT_AVAILABLE',
VALIDATION_FAILED = 'VALIDATION_FAILED',

// ack
ACK_INVALID_PAYLOAD = 'ACK_INVALID_PAYLOAD',

// ai
AI_CONTENT_MISSING = 'AI_CONTENT_MISSING',
AI_INVALID_PARAMETER = 'AI_INVALID_PARAMETER',
AI_INVALID_QUERY_TYPE = 'AI_INVALID_QUERY_TYPE',
AI_NOT_ENABLED = 'AI_NOT_ENABLED',
AI_KEY_EXPIRED = 'AI_KEY_EXPIRED',
AI_PROCESSING = 'AI_PROCESSING',
AI_PROVIDER_DISABLED = 'AI_PROVIDER_DISABLED',
AI_PROVIDER_NOT_FOUND = 'AI_PROVIDER_NOT_FOUND',
AI_REVIEW_NOT_ENABLED = 'AI_REVIEW_NOT_ENABLED',
AI_RESULT_PARSING_ERROR = 'AI_RESULT_PARSING_ERROR',
AI_SERVICE_ERROR = 'AI_SERVICE_ERROR',
AI_TASK_NOT_FOUND = 'AI_TASK_NOT_FOUND',
AI_TASK_ALREADY_COMPLETED = 'AI_TASK_ALREADY_COMPLETED',
AI_TASK_CANNOT_RETRY = 'AI_TASK_CANNOT_RETRY',
AI_TRANSLATION_NOT_FOUND = 'AI_TRANSLATION_NOT_FOUND',

// auth
AUTH_DEVICE_FLOW_PENDING = 'AUTH_DEVICE_FLOW_PENDING',
AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
AUTH_NOT_LOGGED_IN = 'AUTH_NOT_LOGGED_IN',
AUTH_SESSION_EXPIRED = 'AUTH_SESSION_EXPIRED',
AUTH_TOKEN_NOT_FOUND = 'AUTH_TOKEN_NOT_FOUND',
AUTH_CHALLENGE_MISSING = 'AUTH_CHALLENGE_MISSING',
AUTH_CHALLENGE_EXPIRED = 'AUTH_CHALLENGE_EXPIRED',
AUTH_REGISTRATION_MISSING = 'AUTH_REGISTRATION_MISSING',
AUTH_USERNAME_INCORRECT = 'AUTH_USERNAME_INCORRECT',
AUTH_PASSWORD_INCORRECT = 'AUTH_PASSWORD_INCORRECT',
AUTH_SESSION_NOT_FOUND = 'AUTH_SESSION_NOT_FOUND',
AUTH_USER_ID_NOT_FOUND = 'AUTH_USER_ID_NOT_FOUND',
AUTH_FAILED = 'AUTH_FAILED',
AUTH_TOKEN_INVALID = 'AUTH_TOKEN_INVALID',
PASSWORD_LOGIN_DISABLED = 'PASSWORD_LOGIN_DISABLED',
PASSWORD_SAME_AS_OLD = 'PASSWORD_SAME_AS_OLD',

// category
CATEGORY_NOT_FOUND = 'CATEGORY_NOT_FOUND',
CATEGORY_HAS_POSTS = 'CATEGORY_HAS_POSTS',

// comment
COMMENT_DISABLED = 'COMMENT_DISABLED',
COMMENT_FORBIDDEN = 'COMMENT_FORBIDDEN',
COMMENT_NOT_FOUND = 'COMMENT_NOT_FOUND',
COMMENT_TOO_DEEP = 'COMMENT_TOO_DEEP',
COMMENT_POST_NOT_EXISTS = 'COMMENT_POST_NOT_EXISTS',
COMMENT_IMAGE_CAP_EXCEEDED = 'COMMENT_IMAGE_CAP_EXCEEDED',
COMMENT_UPLOAD_DISABLED = 'COMMENT_UPLOAD_DISABLED',
COMMENT_UPLOAD_FILE_TOO_LARGE = 'COMMENT_UPLOAD_FILE_TOO_LARGE',
COMMENT_UPLOAD_INVALID_MIME = 'COMMENT_UPLOAD_INVALID_MIME',
COMMENT_UPLOAD_FILE_NOT_OWNED = 'COMMENT_UPLOAD_FILE_NOT_OWNED',
COMMENT_UPLOAD_FILE_ALREADY_BOUND = 'COMMENT_UPLOAD_FILE_ALREADY_BOUND',
COMMENT_UPLOAD_RATE_LIMITED = 'COMMENT_UPLOAD_RATE_LIMITED',
COMMENT_UPLOAD_QUOTA_EXCEEDED = 'COMMENT_UPLOAD_QUOTA_EXCEEDED',
COMMENT_UPLOAD_ACCOUNT_TOO_NEW = 'COMMENT_UPLOAD_ACCOUNT_TOO_NEW',
COMMENT_UPLOAD_INSUFFICIENT_COMMENTS = 'COMMENT_UPLOAD_INSUFFICIENT_COMMENTS',

// config
CONFIG_NOT_FOUND = 'CONFIG_NOT_FOUND',
CONFIG_VALIDATION_FAILED = 'CONFIG_VALIDATION_FAILED',

// cron
CRON_NOT_FOUND = 'CRON_NOT_FOUND',
INVALID_CRON_METHOD = 'INVALID_CRON_METHOD',
FUNCTION_NOT_FOUND = 'FUNCTION_NOT_FOUND',

// draft
DRAFT_NOT_FOUND = 'DRAFT_NOT_FOUND',
DRAFT_HISTORY_NOT_FOUND = 'DRAFT_HISTORY_NOT_FOUND',

// document / helper
DOCUMENT_NOT_FOUND = 'DOCUMENT_NOT_FOUND',
HELPER_DOCUMENT_NOT_FOUND = 'HELPER_DOCUMENT_NOT_FOUND',

// email
EMAIL_TEMPLATE_NOT_FOUND = 'EMAIL_TEMPLATE_NOT_FOUND',

// enrichment
ENRICHMENT_BROWSER_MODE_REQUIRED = 'ENRICHMENT_BROWSER_MODE_REQUIRED',
ENRICHMENT_CAPTURE_FAILED = 'ENRICHMENT_CAPTURE_FAILED',
ENRICHMENT_NOT_FOUND = 'ENRICHMENT_NOT_FOUND',
ENRICHMENT_SCREENSHOT_DISABLED = 'ENRICHMENT_SCREENSHOT_DISABLED',

// file
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
FILE_EXISTS = 'FILE_EXISTS',
FILE_RENAME_FAILED = 'FILE_RENAME_FAILED',
FILE_STORAGE_NOT_CONFIGURED = 'FILE_STORAGE_NOT_CONFIGURED',
FILE_UPLOAD_DISABLED = 'FILE_UPLOAD_DISABLED',
FILE_UPLOAD_NOT_AUTHORIZED = 'FILE_UPLOAD_NOT_AUTHORIZED',
MIME_ZIP_REQUIRED = 'MIME_ZIP_REQUIRED',

// init
INIT_ALREADY_COMPLETED = 'INIT_ALREADY_COMPLETED',
INIT_FORBIDDEN = 'INIT_FORBIDDEN',
INIT_INVALID_BODY = 'INIT_INVALID_BODY',
INIT_INVALID_MIME_TYPE = 'INIT_INVALID_MIME_TYPE',

// link
LINK_APPLY_DISABLED = 'LINK_APPLY_DISABLED',
LINK_NOT_FOUND = 'LINK_NOT_FOUND',
LINK_DISABLED = 'LINK_DISABLED',
SUBPATH_LINK_DISABLED = 'SUBPATH_LINK_DISABLED',
DUPLICATE_LINK = 'DUPLICATE_LINK',
LINK_AVATAR_VALIDATION_FAILED = 'LINK_AVATAR_VALIDATION_FAILED',

// meta preset
META_PRESET_KEY_EXISTS = 'META_PRESET_KEY_EXISTS',
META_PRESET_NOT_FOUND = 'META_PRESET_NOT_FOUND',
BUILTIN_PRESET_CANNOT_DELETE = 'BUILTIN_PRESET_CANNOT_DELETE',

// note
NOTE_FORBIDDEN = 'NOTE_FORBIDDEN',
NOTE_NOT_FOUND = 'NOTE_NOT_FOUND',
NOTE_PASSWORD_REQUIRED = 'NOTE_PASSWORD_REQUIRED',

// owner / reader / user
OWNER_NOT_FOUND = 'OWNER_NOT_FOUND',
READER_NOT_FOUND = 'READER_NOT_FOUND',
USER_NOT_EXISTS = 'USER_NOT_EXISTS',
USER_ALREADY_EXISTS = 'USER_ALREADY_EXISTS',

// page
PAGE_NOT_FOUND = 'PAGE_NOT_FOUND',

// post
POST_NOT_FOUND = 'POST_NOT_FOUND',
POST_UNPUBLISHED = 'POST_UNPUBLISHED',
POST_HIDDEN_OR_ENCRYPTED = 'POST_HIDDEN_OR_ENCRYPTED',
POST_RELATED_NOT_EXISTS = 'POST_RELATED_NOT_EXISTS',
POST_SELF_RELATION = 'POST_SELF_RELATION',

// recently
RECENTLY_NOT_FOUND = 'RECENTLY_NOT_FOUND',

// backup
BACKUP_NOT_ENABLED = 'BACKUP_NOT_ENABLED',

// serverless
SERVERLESS_ERROR = 'SERVERLESS_ERROR',
SERVERLESS_NO_PERMISSION = 'SERVERLESS_NO_PERMISSION',

// snippet
SNIPPET_NOT_FOUND = 'SNIPPET_NOT_FOUND',
SNIPPET_EXISTS = 'SNIPPET_EXISTS',
SNIPPET_PRIVATE = 'SNIPPET_PRIVATE',
SNIPPET_INVALID_JSON = 'SNIPPET_INVALID_JSON',
SNIPPET_INVALID_JSON5 = 'SNIPPET_INVALID_JSON5',
SNIPPET_INVALID_YAML = 'SNIPPET_INVALID_YAML',
SNIPPET_INVALID_FUNCTION = 'SNIPPET_INVALID_FUNCTION',

// subscribe
SUBSCRIBE_NOT_ENABLED = 'SUBSCRIBE_NOT_ENABLED',
SUBSCRIBE_TYPE_EMPTY = 'SUBSCRIBE_TYPE_EMPTY',
ALREADY_SUPPORTED = 'ALREADY_SUPPORTED',

// topic
TOPIC_NOT_FOUND = 'TOPIC_NOT_FOUND',

// webhook
WEBHOOK_EVENT_NOT_FOUND = 'WEBHOOK_EVENT_NOT_FOUND',
WEBHOOK_NOT_FOUND = 'WEBHOOK_NOT_FOUND',

// bing
BING_API_FAILED = 'BING_API_FAILED',
BING_KEY_INVALID = 'BING_KEY_INVALID',
BING_DOMAIN_INVALID = 'BING_DOMAIN_INVALID',
}
Loading
Loading