@beechcms/api 0.4.0-preview.11 → 0.4.0-preview.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/index-CewtCjom.css +1 -0
- package/assets/dashboard/assets/index-FQ6JhvRH.js +554 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/migrations/0000_v040_base.sql +213 -213
- package/package.json +2 -2
- package/src/auth/constants.ts +10 -10
- package/src/auth/login.ts +91 -91
- package/src/auth/refresh.ts +127 -127
- package/src/factory.ts +347 -325
- package/src/features/content/constants.ts +10 -10
- package/src/features/content/handlers/create.ts +153 -163
- package/src/features/content/handlers/delete.ts +76 -85
- package/src/features/content/handlers/facets.ts +45 -45
- package/src/features/content/handlers/get.ts +116 -116
- package/src/features/content/handlers/list.ts +88 -88
- package/src/features/content/handlers/update.ts +207 -216
- package/src/features/content/index.ts +20 -20
- package/src/features/draft/draft.handler.ts +272 -272
- package/src/features/draft/index.ts +1 -1
- package/src/features/email/email.provider.ts +38 -38
- package/src/features/email/email.service.ts +80 -80
- package/src/features/email/email.types.ts +98 -98
- package/src/features/email/index.ts +28 -28
- package/src/features/email/providers/resend.ts +63 -63
- package/src/features/email/templates/password-changed.ts +59 -59
- package/src/features/email/templates/password-reset.ts +64 -64
- package/src/features/email/templates/shell.ts +92 -93
- package/src/features/notifications/index.ts +1 -1
- package/src/features/notifications/notifications.handler.ts +130 -88
- package/src/features/password-reset/index.ts +15 -15
- package/src/features/password-reset/request.ts +106 -88
- package/src/features/password-reset/reset.ts +128 -110
- package/src/features/rotate-field/index.ts +1 -1
- package/src/features/rotate-field/rotate-field.handler.ts +132 -82
- package/src/features/rotate-field/rotate-field.schema.ts +13 -9
- package/src/features/schema/schema.handler.ts +16 -16
- package/src/features/settings/settings.handler.ts +414 -267
- package/src/features/setup/index.ts +96 -59
- package/src/features/stats/index.ts +1 -1
- package/src/features/stats/stats.handler.ts +458 -395
- package/src/index.ts +24 -24
- package/src/media-utils.ts +78 -78
- package/src/middleware/repository.middleware.ts +24 -18
- package/src/middleware/storage.middleware.ts +21 -0
- package/src/middleware.ts +67 -67
- package/src/public/access-policy.ts +23 -23
- package/src/public/api-key-middleware.ts +53 -53
- package/src/public/index.ts +12 -12
- package/src/public/problem-details.ts +48 -42
- package/src/public/public-add.ts +166 -166
- package/src/public/public-edit.ts +158 -158
- package/src/public/public-errors.ts +15 -15
- package/src/public/public-read.ts +216 -216
- package/src/public/public-routes.ts +84 -31
- package/src/public/query-builder.ts +152 -152
- package/src/public/rate-limit-middleware.ts +42 -42
- package/src/public/response-builder.ts +26 -26
- package/src/public/sanitize.ts +65 -65
- package/src/public/slug-utils.ts +14 -14
- package/src/search-utils.ts +192 -192
- package/src/search.ts +72 -72
- package/src/shared/activity-logger.ts +79 -79
- package/src/shared/apply-policies.ts +63 -63
- package/src/shared/base.repository.d1.ts +28 -28
- package/src/shared/content-utils.ts +82 -108
- package/src/shared/content.repository.d1.ts +382 -382
- package/src/shared/fts-sync.ts +4 -4
- package/src/shared/idempotency.repository.d1.ts +56 -45
- package/src/shared/media.repository.d1.ts +64 -0
- package/src/shared/notification-service.ts +56 -56
- package/src/shared/query-utils.ts +137 -137
- package/src/shared/storage/factory.ts +40 -0
- package/src/shared/storage/r2-binding-bucket.ts +81 -0
- package/src/shared/storage/s3-bucket.ts +163 -0
- package/src/shared/storage-utils.ts +36 -36
- package/src/shared/system-stats.repository.d1.ts +44 -0
- package/src/types.ts +46 -41
- package/src/upload.ts +179 -331
- package/src/widget.ts +349 -349
- package/assets/dashboard/assets/index-9Ch2xWJr.js +0 -554
- package/assets/dashboard/assets/index-ye3325L9.css +0 -1
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import { Context } from 'hono'
|
|
2
|
-
import { applyVisibility } from '../../../shared/apply-policies'
|
|
3
|
-
import { publicProblem } from '../../../public/problem-details'
|
|
4
|
-
import { CONTENT_ERRORS } from '../constants'
|
|
5
|
-
import { AppEnv } from '../../../types'
|
|
6
|
-
import { EntryNotFoundError } from '@beechcms/core'
|
|
7
|
-
|
|
8
|
-
export async function getByIdHandler(context: Context<AppEnv>) {
|
|
9
|
-
const slug = context.req.param('slug')
|
|
10
|
-
const id = context.req.param('id')
|
|
11
|
-
if (!slug || !id) {
|
|
12
|
-
return publicProblem(context, {
|
|
13
|
-
type: 'content-invalid-slug-or-id',
|
|
14
|
-
title: 'Bad Request',
|
|
15
|
-
status: 400,
|
|
16
|
-
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const seed = context.get('getSeed')(slug)
|
|
21
|
-
if (!seed) {
|
|
22
|
-
return publicProblem(context, {
|
|
23
|
-
type: 'content-seed-not-found',
|
|
24
|
-
title: 'Not Found',
|
|
25
|
-
status: 404,
|
|
26
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const repository = context.get('repository')
|
|
32
|
-
const item = await repository.findById(seed, id)
|
|
33
|
-
|
|
34
|
-
let hasPendingDraft = false
|
|
35
|
-
if (seed.allowDrafts) {
|
|
36
|
-
hasPendingDraft = await repository.hasDraft(seed, id)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return context.json({
|
|
40
|
-
...item,
|
|
41
|
-
has_pending_draft: hasPendingDraft,
|
|
42
|
-
data: applyVisibility(item, seed)
|
|
43
|
-
})
|
|
44
|
-
} catch (error) {
|
|
45
|
-
if (error instanceof EntryNotFoundError) {
|
|
46
|
-
return publicProblem(context, {
|
|
47
|
-
type: 'content-not-found',
|
|
48
|
-
title: 'Not Found',
|
|
49
|
-
status: 404,
|
|
50
|
-
detail: CONTENT_ERRORS.NOT_FOUND
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
console.error('Content detail error:', error)
|
|
54
|
-
return publicProblem(context, {
|
|
55
|
-
type: 'content-database-error',
|
|
56
|
-
title: 'Internal Server Error',
|
|
57
|
-
status: 500,
|
|
58
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function getBySlugHandler(context: Context<AppEnv>) {
|
|
64
|
-
const schemaSlug = context.req.param('schema_slug')
|
|
65
|
-
const entrySlug = context.req.param('entry_slug')
|
|
66
|
-
if (!schemaSlug || !entrySlug) {
|
|
67
|
-
return publicProblem(context, {
|
|
68
|
-
type: 'content-invalid-slug-or-id',
|
|
69
|
-
title: 'Bad Request',
|
|
70
|
-
status: 400,
|
|
71
|
-
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const seed = context.get('getSeed')(schemaSlug)
|
|
76
|
-
if (!seed) {
|
|
77
|
-
return publicProblem(context, {
|
|
78
|
-
type: 'content-seed-not-found',
|
|
79
|
-
title: 'Not Found',
|
|
80
|
-
status: 404,
|
|
81
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const repository = context.get('repository')
|
|
87
|
-
const item = await repository.findBySlug(seed, entrySlug)
|
|
88
|
-
|
|
89
|
-
let hasPendingDraft = false
|
|
90
|
-
if (seed.allowDrafts) {
|
|
91
|
-
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return context.json({
|
|
95
|
-
...item,
|
|
96
|
-
has_pending_draft: hasPendingDraft,
|
|
97
|
-
data: applyVisibility(item, seed)
|
|
98
|
-
})
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (error instanceof EntryNotFoundError) {
|
|
101
|
-
return publicProblem(context, {
|
|
102
|
-
type: 'content-not-found',
|
|
103
|
-
title: 'Not Found',
|
|
104
|
-
status: 404,
|
|
105
|
-
detail: CONTENT_ERRORS.NOT_FOUND
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
console.error('Content by-slug error:', error)
|
|
109
|
-
return publicProblem(context, {
|
|
110
|
-
type: 'content-database-error',
|
|
111
|
-
title: 'Internal Server Error',
|
|
112
|
-
status: 500,
|
|
113
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
}
|
|
1
|
+
import { Context } from 'hono'
|
|
2
|
+
import { applyVisibility } from '../../../shared/apply-policies'
|
|
3
|
+
import { publicProblem } from '../../../public/problem-details'
|
|
4
|
+
import { CONTENT_ERRORS } from '../constants'
|
|
5
|
+
import { AppEnv } from '../../../types'
|
|
6
|
+
import { EntryNotFoundError } from '@beechcms/core'
|
|
7
|
+
|
|
8
|
+
export async function getByIdHandler(context: Context<AppEnv>) {
|
|
9
|
+
const slug = context.req.param('slug')
|
|
10
|
+
const id = context.req.param('id')
|
|
11
|
+
if (!slug || !id) {
|
|
12
|
+
return publicProblem(context, {
|
|
13
|
+
type: 'content-invalid-slug-or-id',
|
|
14
|
+
title: 'Bad Request',
|
|
15
|
+
status: 400,
|
|
16
|
+
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const seed = context.get('getSeed')(slug)
|
|
21
|
+
if (!seed) {
|
|
22
|
+
return publicProblem(context, {
|
|
23
|
+
type: 'content-seed-not-found',
|
|
24
|
+
title: 'Not Found',
|
|
25
|
+
status: 404,
|
|
26
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const repository = context.get('repository')
|
|
32
|
+
const item = await repository.findById(seed, id)
|
|
33
|
+
|
|
34
|
+
let hasPendingDraft = false
|
|
35
|
+
if (seed.allowDrafts) {
|
|
36
|
+
hasPendingDraft = await repository.hasDraft(seed, id)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return context.json({
|
|
40
|
+
...item,
|
|
41
|
+
has_pending_draft: hasPendingDraft,
|
|
42
|
+
data: applyVisibility(item, seed)
|
|
43
|
+
})
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof EntryNotFoundError) {
|
|
46
|
+
return publicProblem(context, {
|
|
47
|
+
type: 'content-not-found',
|
|
48
|
+
title: 'Not Found',
|
|
49
|
+
status: 404,
|
|
50
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
console.error('Content detail error:', error)
|
|
54
|
+
return publicProblem(context, {
|
|
55
|
+
type: 'content-database-error',
|
|
56
|
+
title: 'Internal Server Error',
|
|
57
|
+
status: 500,
|
|
58
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function getBySlugHandler(context: Context<AppEnv>) {
|
|
64
|
+
const schemaSlug = context.req.param('schema_slug')
|
|
65
|
+
const entrySlug = context.req.param('entry_slug')
|
|
66
|
+
if (!schemaSlug || !entrySlug) {
|
|
67
|
+
return publicProblem(context, {
|
|
68
|
+
type: 'content-invalid-slug-or-id',
|
|
69
|
+
title: 'Bad Request',
|
|
70
|
+
status: 400,
|
|
71
|
+
detail: CONTENT_ERRORS.INVALID_SLUG_OR_ID
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const seed = context.get('getSeed')(schemaSlug)
|
|
76
|
+
if (!seed) {
|
|
77
|
+
return publicProblem(context, {
|
|
78
|
+
type: 'content-seed-not-found',
|
|
79
|
+
title: 'Not Found',
|
|
80
|
+
status: 404,
|
|
81
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const repository = context.get('repository')
|
|
87
|
+
const item = await repository.findBySlug(seed, entrySlug)
|
|
88
|
+
|
|
89
|
+
let hasPendingDraft = false
|
|
90
|
+
if (seed.allowDrafts) {
|
|
91
|
+
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return context.json({
|
|
95
|
+
...item,
|
|
96
|
+
has_pending_draft: hasPendingDraft,
|
|
97
|
+
data: applyVisibility(item, seed)
|
|
98
|
+
})
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof EntryNotFoundError) {
|
|
101
|
+
return publicProblem(context, {
|
|
102
|
+
type: 'content-not-found',
|
|
103
|
+
title: 'Not Found',
|
|
104
|
+
status: 404,
|
|
105
|
+
detail: CONTENT_ERRORS.NOT_FOUND
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
console.error('Content by-slug error:', error)
|
|
109
|
+
return publicProblem(context, {
|
|
110
|
+
type: 'content-database-error',
|
|
111
|
+
title: 'Internal Server Error',
|
|
112
|
+
status: 500,
|
|
113
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { Context } from 'hono'
|
|
2
|
-
import { parsePositiveInt, parseQueryFilters, cleanStr, toEngineFilters } from '../../../shared/query-utils'
|
|
3
|
-
import { applyVisibility } from '../../../shared/apply-policies'
|
|
4
|
-
import { publicProblem } from '../../../public/problem-details'
|
|
5
|
-
import { CONTENT_ERRORS } from '../constants'
|
|
6
|
-
import { AppEnv } from '../../../types'
|
|
7
|
-
|
|
8
|
-
export async function listHandler(context: Context<AppEnv>) {
|
|
9
|
-
const slug = context.req.param('slug')
|
|
10
|
-
if (!slug) {
|
|
11
|
-
return publicProblem(context, {
|
|
12
|
-
type: 'content-invalid-slug',
|
|
13
|
-
title: 'Bad Request',
|
|
14
|
-
status: 400,
|
|
15
|
-
detail: CONTENT_ERRORS.INVALID_SLUG
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const seed = context.get('getSeed')(slug)
|
|
20
|
-
if (!seed) {
|
|
21
|
-
return publicProblem(context, {
|
|
22
|
-
type: 'content-seed-not-found',
|
|
23
|
-
title: 'Not Found',
|
|
24
|
-
status: 404,
|
|
25
|
-
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const query = context.req.query()
|
|
31
|
-
const search = cleanStr(query.search) ?? ''
|
|
32
|
-
const sortBy = cleanStr(query.sortBy) ?? ''
|
|
33
|
-
const sortDirRaw = cleanStr(query.sortDir)?.toLowerCase() ?? 'asc'
|
|
34
|
-
const rawFilters = parseQueryFilters(query.filters)
|
|
35
|
-
const engineFilters = toEngineFilters(rawFilters)
|
|
36
|
-
|
|
37
|
-
// Note: repository doesn't yet support has_pending_draft filter/column natively
|
|
38
|
-
// in findMany without SQL manipulation. We'll stick to basic findMany for now
|
|
39
|
-
// and might need to enhance the repository if this feature is critical for v1 of this refactor.
|
|
40
|
-
// The legacy code was doing a lot of SQL injection here.
|
|
41
|
-
|
|
42
|
-
const page = parsePositiveInt(query.page, 1)
|
|
43
|
-
const limit = Math.min(parsePositiveInt(query.limit, 25), 100)
|
|
44
|
-
const offset = (page - 1) * limit
|
|
45
|
-
|
|
46
|
-
const orderBy = sortBy
|
|
47
|
-
? { column: sortBy, dir: (sortDirRaw === 'desc' ? 'DESC' : 'ASC') as 'ASC' | 'DESC' }
|
|
48
|
-
: undefined
|
|
49
|
-
|
|
50
|
-
const repository = context.get('repository')
|
|
51
|
-
const { items, total } = await repository.findMany(seed, {
|
|
52
|
-
filters: engineFilters,
|
|
53
|
-
orderBy,
|
|
54
|
-
search: search || undefined,
|
|
55
|
-
pagination: { limit, offset },
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const entries = await Promise.all(items.map(async (item) => {
|
|
59
|
-
// Check for pending draft if allowed
|
|
60
|
-
let hasPendingDraft = false
|
|
61
|
-
if (seed.allowDrafts) {
|
|
62
|
-
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
...item,
|
|
67
|
-
has_pending_draft: hasPendingDraft,
|
|
68
|
-
data: applyVisibility(item, seed) // Repository returns "pure" data including system fields
|
|
69
|
-
}
|
|
70
|
-
}))
|
|
71
|
-
|
|
72
|
-
// If no query params (except slug), return array directly (legacy compatibility)
|
|
73
|
-
const hasQueryParams = Boolean(search) || Boolean(sortBy) || Boolean(query.filters) || query.page !== undefined || query.limit !== undefined
|
|
74
|
-
if (!hasQueryParams) {
|
|
75
|
-
return context.json(entries)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return context.json({ items: entries, total, page, limit })
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Content list error:', error)
|
|
81
|
-
return publicProblem(context, {
|
|
82
|
-
type: 'content-database-error',
|
|
83
|
-
title: 'Internal Server Error',
|
|
84
|
-
status: 500,
|
|
85
|
-
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
import { Context } from 'hono'
|
|
2
|
+
import { parsePositiveInt, parseQueryFilters, cleanStr, toEngineFilters } from '../../../shared/query-utils'
|
|
3
|
+
import { applyVisibility } from '../../../shared/apply-policies'
|
|
4
|
+
import { publicProblem } from '../../../public/problem-details'
|
|
5
|
+
import { CONTENT_ERRORS } from '../constants'
|
|
6
|
+
import { AppEnv } from '../../../types'
|
|
7
|
+
|
|
8
|
+
export async function listHandler(context: Context<AppEnv>) {
|
|
9
|
+
const slug = context.req.param('slug')
|
|
10
|
+
if (!slug) {
|
|
11
|
+
return publicProblem(context, {
|
|
12
|
+
type: 'content-invalid-slug',
|
|
13
|
+
title: 'Bad Request',
|
|
14
|
+
status: 400,
|
|
15
|
+
detail: CONTENT_ERRORS.INVALID_SLUG
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const seed = context.get('getSeed')(slug)
|
|
20
|
+
if (!seed) {
|
|
21
|
+
return publicProblem(context, {
|
|
22
|
+
type: 'content-seed-not-found',
|
|
23
|
+
title: 'Not Found',
|
|
24
|
+
status: 404,
|
|
25
|
+
detail: CONTENT_ERRORS.SEED_NOT_FOUND
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const query = context.req.query()
|
|
31
|
+
const search = cleanStr(query.search) ?? ''
|
|
32
|
+
const sortBy = cleanStr(query.sortBy) ?? ''
|
|
33
|
+
const sortDirRaw = cleanStr(query.sortDir)?.toLowerCase() ?? 'asc'
|
|
34
|
+
const rawFilters = parseQueryFilters(query.filters)
|
|
35
|
+
const engineFilters = toEngineFilters(rawFilters)
|
|
36
|
+
|
|
37
|
+
// Note: repository doesn't yet support has_pending_draft filter/column natively
|
|
38
|
+
// in findMany without SQL manipulation. We'll stick to basic findMany for now
|
|
39
|
+
// and might need to enhance the repository if this feature is critical for v1 of this refactor.
|
|
40
|
+
// The legacy code was doing a lot of SQL injection here.
|
|
41
|
+
|
|
42
|
+
const page = parsePositiveInt(query.page, 1)
|
|
43
|
+
const limit = Math.min(parsePositiveInt(query.limit, 25), 100)
|
|
44
|
+
const offset = (page - 1) * limit
|
|
45
|
+
|
|
46
|
+
const orderBy = sortBy
|
|
47
|
+
? { column: sortBy, dir: (sortDirRaw === 'desc' ? 'DESC' : 'ASC') as 'ASC' | 'DESC' }
|
|
48
|
+
: undefined
|
|
49
|
+
|
|
50
|
+
const repository = context.get('repository')
|
|
51
|
+
const { items, total } = await repository.findMany(seed, {
|
|
52
|
+
filters: engineFilters,
|
|
53
|
+
orderBy,
|
|
54
|
+
search: search || undefined,
|
|
55
|
+
pagination: { limit, offset },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const entries = await Promise.all(items.map(async (item) => {
|
|
59
|
+
// Check for pending draft if allowed
|
|
60
|
+
let hasPendingDraft = false
|
|
61
|
+
if (seed.allowDrafts) {
|
|
62
|
+
hasPendingDraft = await repository.hasDraft(seed, item.id)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...item,
|
|
67
|
+
has_pending_draft: hasPendingDraft,
|
|
68
|
+
data: applyVisibility(item, seed) // Repository returns "pure" data including system fields
|
|
69
|
+
}
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
// If no query params (except slug), return array directly (legacy compatibility)
|
|
73
|
+
const hasQueryParams = Boolean(search) || Boolean(sortBy) || Boolean(query.filters) || query.page !== undefined || query.limit !== undefined
|
|
74
|
+
if (!hasQueryParams) {
|
|
75
|
+
return context.json(entries)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return context.json({ items: entries, total, page, limit })
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Content list error:', error)
|
|
81
|
+
return publicProblem(context, {
|
|
82
|
+
type: 'content-database-error',
|
|
83
|
+
title: 'Internal Server Error',
|
|
84
|
+
status: 500,
|
|
85
|
+
detail: CONTENT_ERRORS.DATABASE_ERROR
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|