Skip to content

Commit 6e0f2dc

Browse files
authored
Merge pull request #57 from techulus/develop
Validate post and page creation payload
2 parents 7b6b941 + 639d359 commit 6e0f2dc

File tree

12 files changed

+102
-58
lines changed

12 files changed

+102
-58
lines changed

apps/page/components/subscribe-prompt.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ export default function SubscribePrompt({
2121

2222
const pageUrl = useMemo(() => getPageUrl(page, settings), [page, settings]);
2323

24-
const NewPageSchema = Yup.object().shape({
24+
const InputSchema = Yup.object().shape({
2525
email: Yup.string().email().required("Enter a valid email"),
2626
});
2727

2828
const formik = useFormik({
2929
initialValues: {
3030
email: "",
3131
},
32-
validationSchema: NewPageSchema,
32+
validationSchema: InputSchema,
3333
onSubmit: async (values) => {
3434
setLoading(true);
3535
try {

apps/web/components/forms/page-form.component.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,19 @@
1+
import {
2+
IPage,
3+
PageType,
4+
PageTypeToLabel
5+
} from "@changes-page/supabase/types/page";
6+
import { Spinner, SpinnerWithSpacing } from "@changes-page/ui";
17
import classNames from "classnames";
28
import { FormikProps, useFormik } from "formik";
39
import { useRouter } from "next/router";
410
import { useEffect, useState } from "react";
511
import slugify from "slugify";
6-
import { InferType, object, string } from "yup";
7-
import {
8-
IPage,
9-
PageType,
10-
PageTypeToLabel,
11-
URL_SLUG_REGEX,
12-
} from "@changes-page/supabase/types/page";
12+
import { InferType } from "yup";
13+
import { NewPageSchema } from "../../data/schema";
1314
import { PrimaryButton, SecondaryButton } from "../core/buttons.component";
14-
import { Spinner, SpinnerWithSpacing } from "@changes-page/ui";
1515
import { InfoMessage, InlineErrorMessage } from "./notification.component";
1616

17-
export const NewPageSchema = object().shape({
18-
url_slug: string()
19-
.min(4, "Too Short!")
20-
.max(24, "Too Long!")
21-
.required("Enter a valid url")
22-
.matches(URL_SLUG_REGEX, "Enter a valid url"),
23-
title: string()
24-
.required("Enter a valid title")
25-
.min(2, "Title too Short!")
26-
.max(50, "Title too Long!"),
27-
description: string()
28-
.min(2, "Description too Short!")
29-
.max(500, "Description too Long!"),
30-
type: string().required("Enter a valid type"),
31-
});
32-
3317
export type PageFormikForm = FormikProps<InferType<typeof NewPageSchema>>;
3418

3519
export default function PageFormComponent({

apps/web/components/forms/post-form.component.tsx

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { useFormik } from "formik";
1919
import { Fragment, useCallback, useEffect, useMemo, useState } from "react";
2020
import ReactMarkdown from "react-markdown";
2121
import { v4 } from "uuid";
22-
import { InferType, array, boolean, mixed, object, string } from "yup";
22+
import { InferType } from "yup";
23+
import { NewPostSchema } from "../../data/schema";
2324
import { track } from "../../utils/analytics";
2425
import { useUserData } from "../../utils/useUser";
2526
import { PrimaryButton } from "../core/buttons.component";
@@ -31,30 +32,6 @@ import AiSuggestTitlePromptDialogComponent from "../dialogs/ai-suggest-title-pro
3132
import DateTimePromptDialog from "../dialogs/date-time-prompt-dialog.component";
3233
import SwitchComponent from "./switch.component";
3334

34-
export const NewPostSchema = object().shape({
35-
title: string()
36-
.required("Title cannot be empty")
37-
.min(2, "Title too Short!")
38-
.max(75, "Title too Long!"),
39-
content: string()
40-
.required("Content cannot be empty")
41-
.min(2, "Content too Short!")
42-
.max(9669, "Content too Long!"),
43-
tags: array()
44-
.of(mixed<PostType>().oneOf(Object.values(PostType)))
45-
.required("Enter valid tags"),
46-
status: mixed<PostStatus>()
47-
.oneOf(Object.values(PostStatus))
48-
.required("Enter valid status"),
49-
page_id: string(),
50-
images_folder: string(),
51-
publish_at: string().optional().nullable(),
52-
publication_date: string().optional().nullable(),
53-
allow_reactions: boolean(),
54-
email_notified: boolean(),
55-
notes: string().optional().nullable(),
56-
});
57-
5835
export type PostFormikForm = InferType<typeof NewPostSchema>;
5936

6037
export default function PostFormComponent({

apps/web/components/layout/blog-layout.component.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DateTime } from "@changes-page/utils";
2+
import { ArrowLeftIcon } from "@heroicons/react/solid";
23
import classNames from "classnames";
34
import Head from "next/head";
45
import Image from "next/image";
@@ -146,9 +147,10 @@ export default function BlogLayout({
146147
<h1>
147148
<Link
148149
href={ROUTES.BLOG}
149-
className="block text-center text-lg font-semibold text-indigo-600 dark:text-indigo-400 uppercase"
150+
className="block text-center text-md font-semibold text-indigo-600 dark:text-indigo-400"
150151
>
151-
Blog
152+
<ArrowLeftIcon className="inline w-4 h-4 -mt-1 font-semibold" />{" "}
153+
Back to blog
152154
</Link>
153155
<span className="mt-2 block text-center text-3xl font-bold leading-8 tracking-tight text-gray-900 dark:text-gray-50 sm:text-4xl hero">
154156
{title}
@@ -198,7 +200,7 @@ export default function BlogLayout({
198200
/>
199201
) : null}
200202

201-
<div className="blog-content-override prose dark:prose-invert prose-indigo mx-auto mt-6">
203+
<div className="blog-content-override prose dark:prose-invert prose-indigo mx-auto mt-6 max-w-[70ch]">
202204
<ReactMarkdown
203205
rehypePlugins={[
204206
rehypeRaw,

apps/web/data/schema.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { PostStatus, PostType, URL_SLUG_REGEX } from "@changes-page/supabase/types/page";
2+
import { array, boolean, mixed, object, string } from "yup";
3+
4+
export const NewPageSchema = object().shape({
5+
url_slug: string()
6+
.min(4, "Too Short!")
7+
.max(24, "Too Long!")
8+
.required("Enter a valid url")
9+
.matches(URL_SLUG_REGEX, "Enter a valid url"),
10+
title: string()
11+
.required("Enter a valid title")
12+
.min(2, "Title too Short!")
13+
.max(50, "Title too Long!"),
14+
description: string()
15+
.min(2, "Description too Short!")
16+
.max(500, "Description too Long!"),
17+
type: string().required("Enter a valid type"),
18+
});
19+
20+
export const NewPostSchema = object().shape({
21+
title: string()
22+
.required("Title cannot be empty")
23+
.min(2, "Title too Short!")
24+
.max(75, "Title too Long!"),
25+
content: string()
26+
.required("Content cannot be empty")
27+
.min(2, "Content too Short!")
28+
.max(9669, "Content too Long!"),
29+
tags: array()
30+
.of(mixed<PostType>().oneOf(Object.values(PostType)))
31+
.required("Enter valid tags"),
32+
status: mixed<PostStatus>()
33+
.oneOf(Object.values(PostStatus))
34+
.required("Enter valid status"),
35+
page_id: string(),
36+
images_folder: string(),
37+
publish_at: string().optional().nullable(),
38+
publication_date: string().optional().nullable(),
39+
allow_reactions: boolean(),
40+
email_notified: boolean(),
41+
notes: string().optional().nullable(),
42+
});

apps/web/pages/api/pages/new.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IErrorResponse } from "@changes-page/supabase/types/api";
22
import { IPage } from "@changes-page/supabase/types/page";
33
import type { NextApiRequest, NextApiResponse } from "next";
4+
import { NewPageSchema } from "../../../data/schema";
45
import { apiRateLimiter } from "../../../utils/rate-limit";
56
import { getSupabaseServerClient } from "../../../utils/supabase/supabase-admin";
67
import {
@@ -28,6 +29,19 @@ const createNewPage = async (
2829
});
2930
}
3031

32+
const isValid = await NewPageSchema.isValid({
33+
url_slug,
34+
title: title.trim(),
35+
description: description.trim(),
36+
type,
37+
});
38+
39+
if (!isValid) {
40+
return res.status(400).json({
41+
error: { statusCode: 400, message: "Invalid request body" },
42+
});
43+
}
44+
3145
console.log("createNewPage", user?.id);
3246

3347
const data = await createPage({

apps/web/pages/api/posts/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PostStatus } from "@changes-page/supabase/types/page";
22
import { NextApiRequest, NextApiResponse } from "next";
3+
import { NewPostSchema } from "../../../data/schema";
34
import { apiRateLimiter } from "../../../utils/rate-limit";
45
import { getSupabaseServerClient } from "../../../utils/supabase/supabase-admin";
56
import { createPost, getUserById } from "../../../utils/useDatabase";
@@ -32,6 +33,26 @@ const createNewPost = async (req: NextApiRequest, res: NextApiResponse) => {
3233
});
3334
}
3435

36+
const isValid = await NewPostSchema.isValid({
37+
page_id,
38+
title: title.trim(),
39+
content: content.trim(),
40+
tags,
41+
status,
42+
images_folder,
43+
publish_at,
44+
notes,
45+
allow_reactions,
46+
email_notified,
47+
publication_date,
48+
});
49+
50+
if (!isValid) {
51+
return res.status(400).json({
52+
error: { statusCode: 400, message: "Invalid request body" },
53+
});
54+
}
55+
3556
console.log("createNewPost", user?.id);
3657

3758
const post = await createPost({

apps/web/pages/pages/[page_id]/[post_id].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { useState } from "react";
55
import { InferType } from "yup";
66
import { notifyError } from "../../../components/core/toast.component";
77
import PostFormComponent, {
8-
NewPostSchema,
98
PostFormikForm,
109
} from "../../../components/forms/post-form.component";
1110
import AuthLayout from "../../../components/layout/auth-layout.component";
1211
import Page from "../../../components/layout/page.component";
1312
import { ROUTES } from "../../../data/routes.data";
13+
import { NewPostSchema } from "../../../data/schema";
1414
import { getSupabaseServerClient } from "../../../utils/supabase/supabase-admin";
1515
import { createOrRetrievePageSettings } from "../../../utils/useDatabase";
1616
import { useUserData } from "../../../utils/useUser";

apps/web/pages/pages/[page_id]/edit.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { useState } from "react";
55
import { InferType } from "yup";
66
import { notifyError } from "../../../components/core/toast.component";
77
import PageFormComponent, {
8-
NewPageSchema,
98
PageFormikForm,
109
} from "../../../components/forms/page-form.component";
1110
import AuthLayout from "../../../components/layout/auth-layout.component";
1211
import Page from "../../../components/layout/page.component";
1312
import { ROUTES } from "../../../data/routes.data";
13+
import { NewPageSchema } from "../../../data/schema";
1414
import { httpPost } from "../../../utils/http";
1515
import { getSupabaseServerClient } from "../../../utils/supabase/supabase-admin";
1616
import { getPage } from "../../../utils/useSSR";

apps/web/pages/pages/[page_id]/new.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { useState } from "react";
55
import { InferType } from "yup";
66
import { notifyError } from "../../../components/core/toast.component";
77
import PostFormComponent, {
8-
NewPostSchema,
98
PostFormikForm,
109
} from "../../../components/forms/post-form.component";
1110
import AuthLayout from "../../../components/layout/auth-layout.component";
1211
import Page from "../../../components/layout/page.component";
1312
import { ROUTES } from "../../../data/routes.data";
13+
import { NewPostSchema } from "../../../data/schema";
1414
import { track } from "../../../utils/analytics";
1515
import { httpPost } from "../../../utils/http";
1616
import { getSupabaseServerClient } from "../../../utils/supabase/supabase-admin";

0 commit comments

Comments
 (0)