kizlo 0.6.1 → 0.8.0

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.
Files changed (88) hide show
  1. package/dist/brand/icons.d.ts +10 -12
  2. package/dist/brand/icons.js +19 -17
  3. package/dist/category/error.js +14 -0
  4. package/dist/category/index.js +77 -0
  5. package/dist/category/schema.js +40 -0
  6. package/dist/category/utils.js +20 -0
  7. package/dist/cli/banner.js +1 -1
  8. package/dist/cli/commands/_setup.js +342 -0
  9. package/dist/cli/commands/create.js +146 -0
  10. package/dist/cli/commands/dev.js +26 -15
  11. package/dist/cli/commands/init.js +53 -300
  12. package/dist/cli/index.js +2 -1
  13. package/dist/cli/presets/generated/templates.js +59 -0
  14. package/dist/cli/presets/nextjs.js +3 -95
  15. package/dist/cli/presets/patch.js +133 -0
  16. package/dist/cli/presets/template.js +36 -0
  17. package/dist/cli/wp/dev.js +16 -1
  18. package/dist/comment/index.js +4 -4
  19. package/dist/context.d.ts +25 -8
  20. package/dist/context.js +15 -5
  21. package/dist/email/service.d.ts +4 -3
  22. package/dist/email/service.js +4 -4
  23. package/dist/index.d.ts +15 -11
  24. package/dist/index.js +10 -4
  25. package/dist/integrations/nextjs/metadata.js +7 -5
  26. package/dist/integrations/nextjs/sitemap.d.ts +8 -7
  27. package/dist/integrations/nextjs/sitemap.js +3 -3
  28. package/dist/menu/index.js +19 -14
  29. package/dist/menu/schema.d.ts +14 -0
  30. package/dist/menu/schema.js +35 -40
  31. package/dist/menu/utils.js +23 -14
  32. package/dist/nextjs/server.d.ts +2 -1
  33. package/dist/nextjs/server.js +2 -1
  34. package/dist/package.js +1 -1
  35. package/dist/page/error.js +20 -0
  36. package/dist/page/index.js +128 -0
  37. package/dist/page/schema.js +75 -0
  38. package/dist/page/utils.js +43 -0
  39. package/dist/post/error.js +4 -14
  40. package/dist/post/index.js +17 -9
  41. package/dist/post/schema.js +37 -66
  42. package/dist/post/utils.js +27 -24
  43. package/dist/router.d.ts +1006 -187
  44. package/dist/router.js +6 -0
  45. package/dist/seo/index.js +7 -44
  46. package/dist/seo/schema.d.ts +10 -6
  47. package/dist/seo/schema.js +7 -5
  48. package/dist/seo/sitemap.d.ts +9 -1
  49. package/dist/seo/sitemap.js +17 -1
  50. package/dist/seo/types.d.ts +17 -20
  51. package/dist/seo/utils.js +9 -7
  52. package/dist/settings/index.js +27 -24
  53. package/dist/settings/service.d.ts +14 -13
  54. package/dist/settings/service.interface.d.ts +2 -2
  55. package/dist/settings/service.js +9 -9
  56. package/dist/shared/middleware.d.ts +4 -4
  57. package/dist/shared/procedure.d.ts +6 -7
  58. package/dist/tag/error.js +14 -0
  59. package/dist/tag/index.js +76 -0
  60. package/dist/tag/schema.js +38 -0
  61. package/dist/tag/utils.js +19 -0
  62. package/dist/taxonomy/service.js +48 -0
  63. package/dist/test/fixtures.d.ts +1 -1
  64. package/dist/test/fixtures.js +1 -1
  65. package/dist/webhook/index.d.ts +2 -2
  66. package/dist/wordpress/category/service.d.ts +28 -0
  67. package/dist/wordpress/category/service.js +55 -0
  68. package/dist/wordpress/category/types.d.ts +120 -0
  69. package/dist/wordpress/category/types.js +15 -0
  70. package/dist/wordpress/comment/service.d.ts +1 -1
  71. package/dist/wordpress/index.d.ts +9 -4
  72. package/dist/wordpress/menu/items/types.d.ts +7 -4
  73. package/dist/wordpress/menu/items/types.js +22 -0
  74. package/dist/wordpress/page/service.d.ts +28 -0
  75. package/dist/wordpress/page/service.js +55 -0
  76. package/dist/wordpress/page/types.d.ts +270 -0
  77. package/dist/wordpress/page/types.js +27 -0
  78. package/dist/wordpress/post/types.d.ts +9 -5
  79. package/dist/wordpress/post/types.js +41 -0
  80. package/dist/wordpress/service.d.ts +7 -1
  81. package/dist/wordpress/service.js +6 -0
  82. package/dist/wordpress/tag/service.d.ts +28 -0
  83. package/dist/wordpress/tag/service.js +55 -0
  84. package/dist/wordpress/tag/types.d.ts +113 -0
  85. package/dist/wordpress/tag/types.js +15 -0
  86. package/package.json +5 -3
  87. package/dist/service.d.ts +0 -19
  88. package/dist/service.js +0 -20
@@ -0,0 +1,128 @@
1
+ import { compare } from "../shared/crypto.js";
2
+ import { createProcedure } from "../shared/procedure.js";
3
+ import { parseIdentifier } from "../shared/identifier.js";
4
+ import { deserializeListMetadata } from "../shared/serialize.js";
5
+ import { getPostTypeService } from "../post-type/service.js";
6
+ import { GET_PAGE_ERROR_MAP, LIST_PAGE_ERROR_MAP } from "./error.js";
7
+ import { ListPageInput, Page, PageList, RetrievePageInput } from "./schema.js";
8
+ import { deserializePage } from "./utils.js";
9
+
10
+ //#region src/page/index.ts
11
+ const PAGE_ROUTER_MAP = {
12
+ get: createProcedure({
13
+ scope: "api",
14
+ method: "GET",
15
+ path: "/pages/{identifier}",
16
+ params: RetrievePageInput.pick({ identifier: true }),
17
+ query: RetrievePageInput.pick({
18
+ password: true,
19
+ previewToken: true
20
+ }).optional(),
21
+ output: Page,
22
+ errors: GET_PAGE_ERROR_MAP
23
+ }, async ({ input, context, errors }) => {
24
+ const postType = getPostTypeService("page", context.wordpress);
25
+ if (input.query?.previewToken) {
26
+ const result = await context.verifyPreviewToken(input.query.previewToken);
27
+ if (!result) throw errors.PAGE_NOT_FOUND();
28
+ const response$1 = await postType.get({
29
+ type: "id",
30
+ value: result.id
31
+ });
32
+ if (response$1.error) switch (response$1.error.code) {
33
+ case "invalid_post_type":
34
+ case "post_type_not_found":
35
+ case "rest_post_invalid_id": throw errors.PAGE_NOT_FOUND();
36
+ case "rest_post_incorrect_password": throw errors.PAGE_PASSWORD_INVALID();
37
+ default:
38
+ context.logger.error("Get page preview unhandled error", response$1.error, {
39
+ id: result.id,
40
+ code: response$1.error.code
41
+ });
42
+ throw errors.INTERNAL_SERVER_ERROR();
43
+ }
44
+ return deserializePage({
45
+ ...response$1.data,
46
+ password: ""
47
+ }, { preview: true });
48
+ }
49
+ const identifier = parseIdentifier(input.params.identifier);
50
+ if (!identifier) throw errors.PAGE_NOT_FOUND();
51
+ const response = await postType.get(identifier);
52
+ if (response.error) switch (response.error.code) {
53
+ case "invalid_post_type":
54
+ case "post_type_not_found":
55
+ case "rest_post_invalid_id": throw errors.PAGE_NOT_FOUND();
56
+ case "rest_post_incorrect_password": throw errors.PAGE_PASSWORD_INVALID();
57
+ default:
58
+ context.logger.error("Get page unhandled error", response.error, {
59
+ identifier,
60
+ code: response.error.code
61
+ });
62
+ throw errors.INTERNAL_SERVER_ERROR();
63
+ }
64
+ const data = response.data;
65
+ if (data.status !== "publish") throw errors.PAGE_NOT_FOUND();
66
+ if (input.query?.password && data.password) {
67
+ if (!await compare(input.query.password, data.password)) throw errors.PAGE_PASSWORD_INVALID();
68
+ return deserializePage({
69
+ ...data,
70
+ password: ""
71
+ });
72
+ }
73
+ return deserializePage(data);
74
+ }),
75
+ list: createProcedure({
76
+ scope: "api",
77
+ method: "GET",
78
+ path: "/pages",
79
+ query: ListPageInput.optional(),
80
+ output: PageList,
81
+ errors: LIST_PAGE_ERROR_MAP
82
+ }, async ({ input, context, errors }) => {
83
+ const q = input.query;
84
+ const postType = getPostTypeService("page", context.wordpress);
85
+ const orderby = q?.orderby === "relevance" && !q?.search || q?.orderby === "include" && q?.include === void 0 ? void 0 : q?.orderby;
86
+ const response = await postType.list({
87
+ context: "edit",
88
+ status: "publish",
89
+ after: input.query?.after,
90
+ author: input.query?.author,
91
+ author_exclude: input.query?.authorExclude,
92
+ before: input.query?.before,
93
+ modified_after: input.query?.modifiedAfter,
94
+ modified_before: input.query?.modifiedBefore,
95
+ exclude: input.query?.exclude,
96
+ include: input.query?.include,
97
+ menu_order: input.query?.menuOrder,
98
+ offset: input.query?.offset,
99
+ order: input.query?.order,
100
+ orderby,
101
+ page: input.query?.page,
102
+ parent: input.query?.parent,
103
+ parent_exclude: input.query?.parentExclude,
104
+ per_page: input.query?.perPage,
105
+ search: input.query?.search,
106
+ search_columns: input.query?.searchColumns,
107
+ slug: input.query?.slug
108
+ });
109
+ if (response.error) switch (response.error.code) {
110
+ case "rest_post_invalid_page_number": throw errors.PAGE_INVALID_PAGE();
111
+ default:
112
+ context.logger.error("List pages unhandled error", response.error, { code: response.error.code });
113
+ throw errors.INTERNAL_SERVER_ERROR();
114
+ }
115
+ const list = context.wordpress.resolveList({
116
+ data: response.data,
117
+ headers: response.headers,
118
+ searchParams: input.query
119
+ });
120
+ return {
121
+ meta: deserializeListMetadata(list.meta),
122
+ items: list.items.map((item) => deserializePage(item))
123
+ };
124
+ })
125
+ };
126
+
127
+ //#endregion
128
+ export { PAGE_ROUTER_MAP };
@@ -0,0 +1,75 @@
1
+ import { IdentifierInput, ListMetadata, ListOrder, Media } from "../shared/schema.js";
2
+ import { WP_PAGE_ORDER_BYES, WP_PAGE_STATUSES } from "../wordpress/page/types.js";
3
+ import { Seo } from "../seo/schema.js";
4
+ import { Metadata, NumberLike, arrayable, lenient } from "@kizlo/shared";
5
+ import z from "zod/v4";
6
+
7
+ //#region src/page/schema.ts
8
+ const PageStatus = z.enum(WP_PAGE_STATUSES).exclude(["trash"]);
9
+ const PageAuthorRef = z.object({
10
+ id: z.number(),
11
+ name: z.string(),
12
+ slug: z.string(),
13
+ avatar: Media.nullable()
14
+ });
15
+ const Page = z.object({
16
+ id: z.number(),
17
+ slug: z.string(),
18
+ url: z.string().nullable(),
19
+ parent: z.number().nullable(),
20
+ menuOrder: z.number(),
21
+ template: z.string(),
22
+ title: z.string().nullable(),
23
+ content: z.string().nullable(),
24
+ excerpt: z.string().nullable(),
25
+ commentsEnabled: z.boolean(),
26
+ protected: z.boolean(),
27
+ preview: z.boolean(),
28
+ status: PageStatus,
29
+ author: PageAuthorRef.nullable(),
30
+ featuredMedia: Media.nullable(),
31
+ seo: Seo.nullable(),
32
+ createdAt: z.number(),
33
+ updatedAt: z.number(),
34
+ meta: Metadata
35
+ });
36
+ const RetrievePageInput = z.object({
37
+ identifier: IdentifierInput,
38
+ password: z.string().optional(),
39
+ previewToken: z.string().optional()
40
+ });
41
+ const PageList = z.object({
42
+ items: z.array(Page),
43
+ meta: ListMetadata
44
+ });
45
+ const PageOrderBy = z.enum(WP_PAGE_ORDER_BYES);
46
+ const PAGE_SEARCH_COLUMNS = [
47
+ "post_title",
48
+ "post_content",
49
+ "post_excerpt"
50
+ ];
51
+ const PageSearchColumn = z.enum(PAGE_SEARCH_COLUMNS);
52
+ const ListPageInput = z.object({
53
+ page: NumberLike.pipe(z.number().int().min(1)).catch(1).optional(),
54
+ perPage: lenient(NumberLike.pipe(z.number().int().min(1).max(100))),
55
+ search: lenient(z.string()),
56
+ after: lenient(z.string()),
57
+ before: lenient(z.string()),
58
+ modifiedAfter: lenient(z.string()),
59
+ modifiedBefore: lenient(z.string()),
60
+ author: lenient(arrayable(NumberLike)),
61
+ authorExclude: lenient(arrayable(NumberLike)),
62
+ exclude: lenient(arrayable(NumberLike)),
63
+ include: lenient(arrayable(NumberLike)),
64
+ offset: lenient(NumberLike.pipe(z.number().int().min(0))),
65
+ order: lenient(ListOrder),
66
+ orderby: lenient(PageOrderBy),
67
+ searchColumns: lenient(z.array(PageSearchColumn)),
68
+ slug: lenient(arrayable(z.string())),
69
+ parent: lenient(arrayable(NumberLike)),
70
+ parentExclude: lenient(arrayable(NumberLike)),
71
+ menuOrder: lenient(NumberLike)
72
+ });
73
+
74
+ //#endregion
75
+ export { ListPageInput, Page, PageList, RetrievePageInput };
@@ -0,0 +1,43 @@
1
+ import { deserializeSeo } from "../seo/utils.js";
2
+ import { resolveWpTimestamp, stringifiedMetaRecord } from "@kizlo/shared";
3
+
4
+ //#region src/page/utils.ts
5
+ function deserializePage(data, options) {
6
+ const locked = data.password.length > 0;
7
+ const kizlo = data.kizlo;
8
+ const title = data.title.rendered;
9
+ return {
10
+ id: data.id,
11
+ url: kizlo.url ?? null,
12
+ parent: data.parent || null,
13
+ menuOrder: data.menu_order ?? 0,
14
+ template: data.template ?? "",
15
+ title: title.length > 0 ? title : null,
16
+ content: locked ? null : data.content?.rendered ?? null,
17
+ excerpt: locked ? null : data.excerpt?.rendered ?? null,
18
+ protected: locked,
19
+ preview: options?.preview ?? false,
20
+ status: data.status === "trash" ? "draft" : data.status,
21
+ featuredMedia: kizlo.featured_media ?? null,
22
+ commentsEnabled: data.comment_status === "open",
23
+ slug: data.slug,
24
+ author: kizlo.author ? {
25
+ id: kizlo.author.id,
26
+ name: kizlo.author.name,
27
+ slug: kizlo.author.slug,
28
+ avatar: kizlo.author.avatar_url ? {
29
+ id: 0,
30
+ alt: kizlo.author.name,
31
+ name: kizlo.author.name,
32
+ src: kizlo.author.avatar_url
33
+ } : null
34
+ } : null,
35
+ seo: kizlo.seo ? deserializeSeo(kizlo.seo) : null,
36
+ createdAt: resolveWpTimestamp(data.date_gmt) ?? resolveWpTimestamp(data.modified_gmt) ?? 0,
37
+ updatedAt: resolveWpTimestamp(data.modified_gmt) ?? 0,
38
+ meta: stringifiedMetaRecord(data.meta ?? {})
39
+ };
40
+ }
41
+
42
+ //#endregion
43
+ export { deserializePage };
@@ -11,20 +11,10 @@ const GET_POST_ERROR_MAP = defineErrorMap({
11
11
  message: "Invalid post password."
12
12
  }
13
13
  });
14
- const LIST_POST_ERROR_MAP = defineErrorMap({
15
- POST_INVALID_PAGE: {
16
- status: 400,
17
- message: "The requested page does not exist."
18
- },
19
- POST_SEARCH_REQUIRED: {
20
- status: 400,
21
- message: "A search term is required."
22
- },
23
- POST_ORDERBY_INCLUDE_MISSING: {
24
- status: 400,
25
- message: "Ordering by 'include' requires the include parameter."
26
- }
27
- });
14
+ const LIST_POST_ERROR_MAP = defineErrorMap({ POST_INVALID_PAGE: {
15
+ status: 400,
16
+ message: "The requested page does not exist."
17
+ } });
28
18
 
29
19
  //#endregion
30
20
  export { GET_POST_ERROR_MAP, LIST_POST_ERROR_MAP };
@@ -1,8 +1,8 @@
1
1
  import { compare } from "../shared/crypto.js";
2
2
  import { createProcedure } from "../shared/procedure.js";
3
+ import { parseIdentifier } from "../shared/identifier.js";
3
4
  import { deserializeListMetadata } from "../shared/serialize.js";
4
5
  import { getPostTypeService } from "../post-type/service.js";
5
- import { parseIdentifier } from "../shared/identifier.js";
6
6
  import { GET_POST_ERROR_MAP, LIST_POST_ERROR_MAP } from "./error.js";
7
7
  import { ListPostInput, Post, PostList, RetrievePostInput } from "./schema.js";
8
8
  import { deserializePost } from "./utils.js";
@@ -21,7 +21,7 @@ const POST_ROUTER_MAP = {
21
21
  output: Post,
22
22
  errors: GET_POST_ERROR_MAP
23
23
  }, async ({ input, context, errors }) => {
24
- const postType = getPostTypeService("post", context.service.wordpress);
24
+ const postType = getPostTypeService("post", context.wordpress);
25
25
  if (input.query?.previewToken) {
26
26
  const result = await context.verifyPreviewToken(input.query.previewToken);
27
27
  if (!result) throw errors.POST_NOT_FOUND();
@@ -30,6 +30,8 @@ const POST_ROUTER_MAP = {
30
30
  value: result.id
31
31
  });
32
32
  if (response$1.error) switch (response$1.error.code) {
33
+ case "invalid_post_type":
34
+ case "post_type_not_found":
33
35
  case "rest_post_invalid_id": throw errors.POST_NOT_FOUND();
34
36
  case "rest_post_incorrect_password": throw errors.POST_PASSWORD_INVALID();
35
37
  default:
@@ -42,12 +44,14 @@ const POST_ROUTER_MAP = {
42
44
  return deserializePost({
43
45
  ...response$1.data,
44
46
  password: ""
45
- });
47
+ }, { preview: true });
46
48
  }
47
49
  const identifier = parseIdentifier(input.params.identifier);
48
50
  if (!identifier) throw errors.POST_NOT_FOUND();
49
51
  const response = await postType.get(identifier);
50
52
  if (response.error) switch (response.error.code) {
53
+ case "invalid_post_type":
54
+ case "post_type_not_found":
51
55
  case "rest_post_invalid_id": throw errors.POST_NOT_FOUND();
52
56
  case "rest_post_incorrect_password": throw errors.POST_PASSWORD_INVALID();
53
57
  default:
@@ -58,6 +62,7 @@ const POST_ROUTER_MAP = {
58
62
  throw errors.INTERNAL_SERVER_ERROR();
59
63
  }
60
64
  const data = response.data;
65
+ if (data.status !== "publish") throw errors.POST_NOT_FOUND();
61
66
  if (input.query?.password && data.password) {
62
67
  if (!await compare(input.query.password, data.password)) throw errors.POST_PASSWORD_INVALID();
63
68
  return deserializePost({
@@ -75,20 +80,25 @@ const POST_ROUTER_MAP = {
75
80
  output: PostList,
76
81
  errors: LIST_POST_ERROR_MAP
77
82
  }, async ({ input, context, errors }) => {
78
- const response = await getPostTypeService("post", context.service.wordpress).list({
83
+ const q = input.query;
84
+ const postType = getPostTypeService("post", context.wordpress);
85
+ const orderby = q?.orderby === "relevance" && !q?.search || q?.orderby === "include" && q?.include === void 0 ? void 0 : q?.orderby;
86
+ const response = await postType.list({
79
87
  context: "edit",
80
88
  status: "publish",
81
89
  after: input.query?.after,
82
90
  author: input.query?.author,
83
91
  author_exclude: input.query?.authorExclude,
84
92
  before: input.query?.before,
93
+ modified_after: input.query?.modifiedAfter,
94
+ modified_before: input.query?.modifiedBefore,
85
95
  categories: input.query?.categories,
86
96
  categories_exclude: input.query?.categoriesExclude,
87
97
  exclude: input.query?.exclude,
88
98
  include: input.query?.include,
89
99
  offset: input.query?.offset,
90
100
  order: input.query?.order,
91
- orderby: input.query?.orderby,
101
+ orderby,
92
102
  page: input.query?.page,
93
103
  per_page: input.query?.perPage,
94
104
  search: input.query?.search,
@@ -101,20 +111,18 @@ const POST_ROUTER_MAP = {
101
111
  });
102
112
  if (response.error) switch (response.error.code) {
103
113
  case "rest_post_invalid_page_number": throw errors.POST_INVALID_PAGE();
104
- case "rest_no_search_term_defined": throw errors.POST_SEARCH_REQUIRED();
105
- case "rest_orderby_include_missing_include": throw errors.POST_ORDERBY_INCLUDE_MISSING();
106
114
  default:
107
115
  context.logger.error("List posts unhandled error", response.error, { code: response.error.code });
108
116
  throw errors.INTERNAL_SERVER_ERROR();
109
117
  }
110
- const list = context.service.wordpress.resolveList({
118
+ const list = context.wordpress.resolveList({
111
119
  data: response.data,
112
120
  headers: response.headers,
113
121
  searchParams: input.query
114
122
  });
115
123
  return {
116
124
  meta: deserializeListMetadata(list.meta),
117
- items: list.items.map(deserializePost)
125
+ items: list.items.map((item) => deserializePost(item))
118
126
  };
119
127
  })
120
128
  };
@@ -1,46 +1,16 @@
1
1
  import { IdentifierInput, ListMetadata, ListOrder, Media } from "../shared/schema.js";
2
+ import { WP_POST_FORMATS, WP_POST_ORDER_BYES, WP_POST_STATUSES, WP_POST_TAX_RELATIONS } from "../wordpress/post/types.js";
2
3
  import { Seo } from "../seo/schema.js";
3
- import { BooleanLike, Metadata, NumberLike, arrayable } from "@kizlo/shared";
4
+ import { BooleanLike, Metadata, NumberLike, arrayable, lenient } from "@kizlo/shared";
4
5
  import z from "zod/v4";
5
6
 
6
7
  //#region src/post/schema.ts
7
- const POST_TYPE_FORMATS = [
8
- "standard",
9
- "aside",
10
- "chat",
11
- "gallery",
12
- "link",
13
- "image",
14
- "quote",
15
- "status",
16
- "video",
17
- "audio"
18
- ];
19
- const PostTypeFormat = z.enum(POST_TYPE_FORMATS);
20
- const POST_STATUSES = [
21
- "publish",
22
- "future",
23
- "draft",
24
- "pending",
25
- "private"
26
- ];
27
- const PostStatus = z.enum(POST_STATUSES);
28
- const POST_COMMENT_STATUSES = [
29
- "approved",
30
- "hold",
31
- "spam",
32
- "trash"
33
- ];
34
- const PostCommentStatus = z.enum(POST_COMMENT_STATUSES);
35
- const POST_COMMENT_TYPES = [
36
- "comment",
37
- "pingback",
38
- "trackback"
39
- ];
40
- const PostCommentType = z.enum(POST_COMMENT_TYPES);
8
+ const PostTypeFormat = z.enum(WP_POST_FORMATS);
9
+ const PostStatus = z.enum(WP_POST_STATUSES).exclude(["trash"]);
41
10
  const PostAuthorRef = z.object({
42
11
  id: z.number(),
43
12
  name: z.string(),
13
+ slug: z.string(),
44
14
  avatar: Media.nullable()
45
15
  });
46
16
  const PostCategoryRef = z.object({
@@ -56,11 +26,15 @@ const PostTagRef = z.object({
56
26
  const Post = z.object({
57
27
  id: z.number(),
58
28
  slug: z.string(),
29
+ url: z.string().nullable(),
30
+ parent: z.number().nullable(),
59
31
  title: z.string().nullable(),
60
32
  content: z.string().nullable(),
61
33
  excerpt: z.string().nullable(),
62
34
  commentsEnabled: z.boolean(),
63
35
  protected: z.boolean(),
36
+ preview: z.boolean(),
37
+ status: PostStatus,
64
38
  format: PostTypeFormat,
65
39
  sticky: z.boolean(),
66
40
  author: PostAuthorRef.nullable(),
@@ -81,39 +55,36 @@ const PostList = z.object({
81
55
  items: z.array(Post),
82
56
  meta: ListMetadata
83
57
  });
84
- const POST_ORDER_BYES = [
85
- "author",
86
- "date",
87
- "id",
88
- "include",
89
- "parent",
90
- "relevance",
91
- "slug",
92
- "include_slugs",
93
- "title"
58
+ const PostOrderBy = z.enum(WP_POST_ORDER_BYES);
59
+ const POST_SEARCH_COLUMNS = [
60
+ "post_title",
61
+ "post_content",
62
+ "post_excerpt"
94
63
  ];
95
- const PostOrderBy = z.enum(POST_ORDER_BYES);
64
+ const PostSearchColumn = z.enum(POST_SEARCH_COLUMNS);
96
65
  const ListPostInput = z.object({
97
- page: NumberLike.optional(),
98
- perPage: NumberLike.optional(),
99
- search: z.string().optional(),
100
- after: z.string().optional(),
101
- before: z.string().optional(),
102
- author: arrayable(NumberLike).optional(),
103
- authorExclude: arrayable(NumberLike).optional(),
104
- exclude: arrayable(NumberLike).optional(),
105
- include: arrayable(NumberLike).optional(),
106
- offset: NumberLike.optional(),
107
- order: ListOrder.optional(),
108
- orderby: PostOrderBy.optional(),
109
- searchColumns: z.array(z.string()).optional(),
110
- slug: arrayable(z.string()).optional(),
111
- taxRelation: z.enum(["AND", "OR"]).optional(),
112
- categories: arrayable(NumberLike).optional(),
113
- categoriesExclude: arrayable(NumberLike).optional(),
114
- tags: arrayable(NumberLike).optional(),
115
- tagsExclude: arrayable(NumberLike).optional(),
116
- sticky: BooleanLike.optional()
66
+ page: NumberLike.pipe(z.number().int().min(1)).catch(1).optional(),
67
+ perPage: lenient(NumberLike.pipe(z.number().int().min(1).max(100))),
68
+ search: lenient(z.string()),
69
+ after: lenient(z.string()),
70
+ before: lenient(z.string()),
71
+ modifiedAfter: lenient(z.string()),
72
+ modifiedBefore: lenient(z.string()),
73
+ author: lenient(arrayable(NumberLike)),
74
+ authorExclude: lenient(arrayable(NumberLike)),
75
+ exclude: lenient(arrayable(NumberLike)),
76
+ include: lenient(arrayable(NumberLike)),
77
+ offset: lenient(NumberLike.pipe(z.number().int().min(0))),
78
+ order: lenient(ListOrder),
79
+ orderby: lenient(PostOrderBy),
80
+ searchColumns: lenient(z.array(PostSearchColumn)),
81
+ slug: lenient(arrayable(z.string())),
82
+ taxRelation: lenient(z.enum(WP_POST_TAX_RELATIONS)),
83
+ categories: lenient(arrayable(NumberLike)),
84
+ categoriesExclude: lenient(arrayable(NumberLike)),
85
+ tags: lenient(arrayable(NumberLike)),
86
+ tagsExclude: lenient(arrayable(NumberLike)),
87
+ sticky: lenient(BooleanLike)
117
88
  });
118
89
 
119
90
  //#endregion
@@ -1,39 +1,42 @@
1
1
  import { deserializeSeo } from "../seo/utils.js";
2
- import { stringifiedMetaRecord } from "@kizlo/shared";
2
+ import { resolveWpTimestamp, stringifiedMetaRecord } from "@kizlo/shared";
3
3
 
4
4
  //#region src/post/utils.ts
5
- function deserializePost(data) {
5
+ function deserializePost(data, options) {
6
+ const locked = data.password.length > 0;
7
+ const kizlo = data.kizlo;
8
+ const title = data.title.rendered;
6
9
  return {
7
10
  id: data.id,
8
- title: data.title.rendered,
9
- content: !data.password.length ? data.content?.rendered ?? null : null,
10
- excerpt: !data.password.length ? data.excerpt.rendered : null,
11
- protected: !!data.password.length,
12
- featuredMedia: data.kizlo?.featured_media ? {
13
- id: data.kizlo.featured_media.id,
14
- alt: data.kizlo.featured_media.alt,
15
- name: data.title.rendered,
16
- src: data.kizlo.featured_media.url
17
- } : null,
11
+ url: kizlo.url ?? null,
12
+ parent: data.parent ?? null,
13
+ title: title.length > 0 ? title : null,
14
+ content: locked ? null : data.content?.rendered ?? null,
15
+ excerpt: locked ? null : data.excerpt?.rendered ?? null,
16
+ protected: locked,
17
+ preview: options?.preview ?? false,
18
+ status: data.status === "trash" ? "draft" : data.status,
19
+ featuredMedia: kizlo.featured_media ?? null,
18
20
  commentsEnabled: data.comment_status === "open",
19
21
  slug: data.slug,
20
22
  sticky: data.sticky ?? false,
21
- tags: data.kizlo.tags ?? [],
22
- categories: data.kizlo.categories ?? [],
23
- author: data.kizlo.author ? {
24
- id: data.kizlo.author.id,
25
- name: data.kizlo.author.name,
26
- avatar: data.kizlo.author.avatar_url ? {
23
+ tags: kizlo.tags ?? [],
24
+ categories: kizlo.categories ?? [],
25
+ author: kizlo.author ? {
26
+ id: kizlo.author.id,
27
+ name: kizlo.author.name,
28
+ slug: kizlo.author.slug,
29
+ avatar: kizlo.author.avatar_url ? {
27
30
  id: 0,
28
- alt: data.kizlo.author.name,
29
- name: data.kizlo.author.name,
30
- src: data.kizlo.author.avatar_url
31
+ alt: kizlo.author.name,
32
+ name: kizlo.author.name,
33
+ src: kizlo.author.avatar_url
31
34
  } : null
32
35
  } : null,
33
36
  format: data.format ?? "standard",
34
- seo: data.kizlo.seo ? deserializeSeo(data.kizlo.seo) : null,
35
- createdAt: new Date(data.date ?? "").getTime(),
36
- updatedAt: new Date(data.modified ?? "").getTime(),
37
+ seo: kizlo.seo ? deserializeSeo(kizlo.seo) : null,
38
+ createdAt: resolveWpTimestamp(data.date_gmt) ?? resolveWpTimestamp(data.modified_gmt) ?? 0,
39
+ updatedAt: resolveWpTimestamp(data.modified_gmt) ?? 0,
37
40
  meta: stringifiedMetaRecord(data.meta ?? {})
38
41
  };
39
42
  }