fix: create server actions
This commit is contained in:
parent
e1cdba824a
commit
62f0e75abd
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
@ -1,14 +1,16 @@
|
|||
"use server"
|
||||
import { Genre, Story } from "@prisma/client"
|
||||
import { Genre, Pub, Story, Sub } from "@prisma/client"
|
||||
import prisma from "./db"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { redirect } from "next/navigation"
|
||||
import { z } from "zod"
|
||||
import { storySchema } from "app/ui/forms/schemas"
|
||||
import { pubSchema } from "app/ui/forms/schemas"
|
||||
import { subSchema } from "app/ui/forms/schemas"
|
||||
|
||||
//TODO - data validation, error handling, unauthorized access handling
|
||||
|
||||
export async function createStory(data: Story & { genres: number[] }): Promise<Story> | undefined {
|
||||
export async function createStory(data: Story & { genres: number[] }): Promise<Story | undefined> {
|
||||
// will return undefined if middleware authorization fails
|
||||
"use server"
|
||||
//Prepare data
|
||||
|
@ -21,11 +23,12 @@ export async function createStory(data: Story & { genres: number[] }): Promise<S
|
|||
const schema = storySchema.omit({ id: true, genres: true })
|
||||
const genreSchema = z.object({ id: z.number() })
|
||||
const genresSchema = z.array(genreSchema)
|
||||
//validate
|
||||
const isSafe = schema.safeParse(storyData) && genresSchema.safeParse(genresArray)
|
||||
if (!isSafe) throw new Error("server-side validation failed")
|
||||
//submit
|
||||
try {
|
||||
//validate
|
||||
schema.safeParse(storyData)
|
||||
genresSchema.safeParse(genresArray)
|
||||
|
||||
//submit
|
||||
const res = await prisma.story.create({ data: storyData })
|
||||
await prisma.story.update({
|
||||
where: { id: res.id },
|
||||
|
@ -33,42 +36,65 @@ export async function createStory(data: Story & { genres: number[] }): Promise<S
|
|||
genres: { set: genresArray }
|
||||
}
|
||||
})
|
||||
revalidatePath("/story")
|
||||
return res
|
||||
} catch (error) {
|
||||
throw new Error("database failure")
|
||||
console.error(error)
|
||||
return undefined
|
||||
}
|
||||
revalidatePath("/story")
|
||||
redirect("/story")
|
||||
}
|
||||
|
||||
|
||||
export async function createPub(data) {
|
||||
export async function createPub(data: Pub & { genres: number[] }): Promise<Pub | undefined> {
|
||||
"use server"
|
||||
//prepare data
|
||||
const pubData = {
|
||||
title: data.title,
|
||||
link: data.link,
|
||||
query_after_days: data.query_after_days
|
||||
}
|
||||
const genresArray = data.genres.map(e => { return { id: e } })
|
||||
const res = await prisma.pub.create({
|
||||
data: {
|
||||
title: data.title,
|
||||
link: data.link,
|
||||
query_after_days: data.query_after_days
|
||||
}
|
||||
})
|
||||
console.log(res)
|
||||
const genresRes = await prisma.pub.update({
|
||||
where: { id: res.id },
|
||||
data:
|
||||
{ genres: { set: genresArray } }
|
||||
})
|
||||
console.log(genresRes)
|
||||
revalidatePath("/publication")
|
||||
redirect("/publication")
|
||||
|
||||
//prepare schemas
|
||||
const schema = pubSchema.omit({ genres: true })
|
||||
const genreSchema = z.object({ id: z.number() })
|
||||
const genresSchema = z.array(genreSchema)
|
||||
|
||||
try {
|
||||
|
||||
//validate
|
||||
schema.parse(pubData)
|
||||
genresSchema.safeParse(genresArray)
|
||||
|
||||
//submit
|
||||
const res = await prisma.pub.create({
|
||||
data: pubData
|
||||
})
|
||||
const genresRes = await prisma.pub.update({
|
||||
where: { id: res.id },
|
||||
data:
|
||||
{ genres: { set: genresArray } }
|
||||
})
|
||||
revalidatePath("/publication")
|
||||
return res
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function createSub(data) {
|
||||
export async function createSub(data: Sub): Promise<Sub | undefined> {
|
||||
"use server"
|
||||
const res = await prisma.sub.create({ data })
|
||||
console.log(res)
|
||||
revalidatePath("/submission")
|
||||
redirect("/submission")
|
||||
|
||||
try {
|
||||
subSchema.parse(data)
|
||||
const res = await prisma.sub.create({ data })
|
||||
revalidatePath("/submission")
|
||||
return res
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import Link from "next/link";
|
|||
export default function LoginForm() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const redirect = searchParams.get("from")
|
||||
const redirect = searchParams.get("from") ?? "submission"
|
||||
const form = useForm<z.infer<typeof loginSchema>>({
|
||||
resolver: zodResolver(loginSchema),
|
||||
})
|
||||
|
|
|
@ -7,7 +7,7 @@ import { PubsWithGenres } from "./page"
|
|||
import { TextInputCell } from "app/ui/tables/inputs/textInput"
|
||||
import { selectCol } from "app/ui/tables/selectColumn"
|
||||
import NumberInputCell from "app/ui/tables/inputs/numberInput"
|
||||
import { formSchema } from "app/ui/forms/pub"
|
||||
import { pubSchema } from "app/ui/forms/schemas"
|
||||
import GenrePickerInputCell from "app/ui/tables/inputs/genrePickerInput"
|
||||
|
||||
|
||||
|
@ -29,14 +29,14 @@ export const columns: ColumnDef<PubsWithGenres>[] = [
|
|||
)
|
||||
},
|
||||
cell: TextInputCell,
|
||||
meta: { formSchema }
|
||||
meta: { formSchema: pubSchema }
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "link",
|
||||
header: "Link",
|
||||
cell: TextInputCell,
|
||||
meta: { formSchema }
|
||||
meta: { formSchema: pubSchema }
|
||||
},
|
||||
|
||||
columnHelper.accessor("genres", {
|
||||
|
@ -51,7 +51,7 @@ export const columns: ColumnDef<PubsWithGenres>[] = [
|
|||
cell: NumberInputCell,
|
||||
meta: {
|
||||
step: 10,
|
||||
formSchema
|
||||
formSchema: pubSchema
|
||||
},
|
||||
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"
|
|||
import GenreBadges from "app/ui/genreBadges"
|
||||
import { selectCol } from "app/ui/tables/selectColumn"
|
||||
import NumberInputCell from "app/ui/tables/inputs/numberInput"
|
||||
import { formSchema } from "app/ui/forms/story"
|
||||
import { storySchema } from "app/ui/forms/schemas"
|
||||
import { TextInputCell } from "app/ui/tables/inputs/textInput"
|
||||
import GenrePickerInputCell from "app/ui/tables/inputs/genrePickerInput"
|
||||
const columnHelper = createColumnHelper<StoryWithGenres>()
|
||||
|
@ -27,7 +27,7 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
|||
)
|
||||
},
|
||||
cell: TextInputCell,
|
||||
meta: { formSchema }
|
||||
meta: { formSchema: storySchema }
|
||||
|
||||
},
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
|||
cell: NumberInputCell,
|
||||
meta: {
|
||||
step: 50,
|
||||
formSchema
|
||||
formSchema: storySchema
|
||||
}
|
||||
},
|
||||
columnHelper.accessor("genres", {
|
||||
|
|
|
@ -19,17 +19,11 @@ import { randomPublicationTitle } from "app/lib/shortStoryTitleGenerator"
|
|||
import { ComponentProps } from "react"
|
||||
import { Genre } from "@prisma/client"
|
||||
import GenrePicker from "./genrePicker"
|
||||
|
||||
export const formSchema = z.object({
|
||||
title: z.string().min(2).max(50),
|
||||
link: z.string(),
|
||||
query_after_days: z.coerce.number().min(30),
|
||||
genres: z.array(z.number()),
|
||||
})
|
||||
import { pubSchema } from "./schemas"
|
||||
|
||||
export default function PubForm({ genres, createPub, className }: ComponentProps<"div"> & { genres: Array<Genre>, createPub: (data: any) => void }) {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
const form = useForm<z.infer<typeof pubSchema>>({
|
||||
resolver: zodResolver(pubSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
link: "",
|
||||
|
@ -38,19 +32,21 @@ export default function PubForm({ genres, createPub, className }: ComponentProps
|
|||
},
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// Do something with the form values.
|
||||
// ✅ This will be type-safe and validated.
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(values, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
createPub(values)
|
||||
console.log(values)
|
||||
async function onSubmit(values: z.infer<typeof pubSchema>) {
|
||||
try {
|
||||
const res = await createPub(values)
|
||||
if (res === undefined) throw new Error("something went wrong")
|
||||
toast({
|
||||
title: "Successfuly submitted:",
|
||||
description: values.title
|
||||
})
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "UH-OH",
|
||||
description: error.message
|
||||
})
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function onErrors(errors) {
|
||||
|
|
|
@ -5,3 +5,49 @@ export const storySchema = z.object({
|
|||
word_count: z.number(),
|
||||
genres: z.object({ id: z.number(), name: z.string() }).array()
|
||||
})
|
||||
|
||||
export const pubSchema = z.object({
|
||||
title: z.string().min(2).max(50),
|
||||
link: z.string(),
|
||||
query_after_days: z.coerce.number().min(30),
|
||||
genres: z.array(z.number()),
|
||||
})
|
||||
|
||||
export const subSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
storyId: z.coerce.number(),
|
||||
pubId: z.coerce.number(),
|
||||
submitted: z.coerce.date().transform((date) => date.toString()),
|
||||
responded: z.coerce.date().transform((date) => {
|
||||
if (date.toString() !== new Date(null).toString()) {
|
||||
return date.toString()
|
||||
}
|
||||
return null
|
||||
}).optional(),
|
||||
responseId: z.coerce.number()
|
||||
})
|
||||
.refine(object => {
|
||||
const submitted = new Date(object.submitted)
|
||||
const responded = object.responded ? new Date(object.responded) : null
|
||||
return responded >= submitted || responded === null
|
||||
},
|
||||
{
|
||||
path: ["responded"],
|
||||
message: "'Responded' must be a later date than 'submitted'"
|
||||
})
|
||||
.refine(object => {
|
||||
if (object.responded) {
|
||||
//there is a 'responded' date and the response is not 'pending'
|
||||
return object.responseId !== 7
|
||||
}
|
||||
if (!object.responded) {
|
||||
//there is not a 'responded' date and the response is pending
|
||||
return object.responseId === 7
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ["responseId"],
|
||||
message: "A pending response cannot have a date, and a non-pending response must have a date"
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -49,14 +49,13 @@ export default function StoryForm({ genres, createStory, className, existingData
|
|||
title: "Successfully submitted:",
|
||||
description: values.title,
|
||||
})
|
||||
//TODO refresh data without reloading page?
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "UH-OH",
|
||||
description: error.message
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,54 +32,15 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { useState } from "react"
|
||||
import { editSubmission } from "app/lib/edit"
|
||||
import { createSub } from "app/lib/create"
|
||||
import { subSchema } from "./schemas"
|
||||
|
||||
export const formSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
storyId: z.coerce.number(),
|
||||
pubId: z.coerce.number(),
|
||||
submitted: z.coerce.date().transform((date) => date.toString()),
|
||||
responded: z.coerce.date().transform((date) => {
|
||||
|
||||
if (date.toString() !== new Date(null).toString()) {
|
||||
return date.toString()
|
||||
}
|
||||
return null
|
||||
}).optional(),
|
||||
responseId: z.coerce.number()
|
||||
})
|
||||
.refine(object => {
|
||||
const submitted = new Date(object.submitted)
|
||||
const responded = object.responded ? new Date(object.responded) : null
|
||||
return responded >= submitted || responded === null
|
||||
},
|
||||
{
|
||||
path: ["responded"],
|
||||
message: "'Responded' must be a later date than 'submitted'"
|
||||
})
|
||||
.refine(object => {
|
||||
if (object.responded) {
|
||||
//there is a 'responded' date and the response is not 'pending'
|
||||
return object.responseId !== 7
|
||||
}
|
||||
if (!object.responded) {
|
||||
//there is not a 'responded' date and the response is pending
|
||||
return object.responseId === 7
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ["responseId"],
|
||||
message: "A pending response cannot have a date, and a non-pending response must have a date"
|
||||
}
|
||||
)
|
||||
|
||||
export type SubForm = z.infer<typeof formSchema>
|
||||
export type SubForm = z.infer<typeof subSchema>
|
||||
|
||||
|
||||
export default function SubmissionForm({ stories, pubs, responses, defaults }) {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
const form = useForm<z.infer<typeof subSchema>>({
|
||||
resolver: zodResolver(subSchema),
|
||||
defaultValues: {
|
||||
responseId: responses[0].id,
|
||||
...defaults
|
||||
|
@ -106,23 +67,18 @@ export default function SubmissionForm({ stories, pubs, responses, defaults }) {
|
|||
|
||||
|
||||
// 2. Define a submit handler.
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// Do something with the form values.
|
||||
// ✅ This will be type-safe and validated.
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(values, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
if (values.id) {
|
||||
editSubmission(values)
|
||||
} else {
|
||||
createSub(values)
|
||||
async function onSubmit(values: z.infer<typeof subSchema>) {
|
||||
try {
|
||||
const res = await createSub(values)
|
||||
if (res === undefined) throw new Error("something went wrong")
|
||||
toast({ title: "Successfully created new submission!" })
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "UH-OH",
|
||||
description: error.message
|
||||
})
|
||||
}
|
||||
console.log(values)
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function onErrors(errors) {
|
||||
|
|
Loading…
Reference in New Issue