Skip to content

Commit

Permalink
refactor: set up serialization of server action errors
Browse files Browse the repository at this point in the history
  • Loading branch information
matejfalat committed May 3, 2024
1 parent df4b261 commit c929e74
Show file tree
Hide file tree
Showing 24 changed files with 194 additions and 177 deletions.
6 changes: 5 additions & 1 deletion src/server/actions/auth/setSignInPassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {db} from '../../db'
import {users} from '../../db/schema/user'
import {requireServerSession} from '../../utils/auth'

export const setSignInPassword = async (password: string) => {
import {serializeServerErrors} from '@/lib/utils/common/error'

const setSignInPasswordAction = async (password: string) => {
const session = await requireServerSession()

const id = session.user.id
Expand All @@ -19,3 +21,5 @@ export const setSignInPassword = async (password: string) => {
.where(eq(users.id, id))
.returning()
}

export const setSignInPassword = serializeServerErrors(setSignInPasswordAction)
20 changes: 9 additions & 11 deletions src/server/actions/auth/signUpWithCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ import {z} from 'zod'
import {db} from '../../db'
import {users} from '../../db/schema/user'

import {getApiFormError, getApiZodError} from '@/lib/utils/common/error'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {signUpSchema} from '@/server/utils/validations/schemas'
import {FormError} from '@/lib/types/error'

export type SignUpRequest = z.infer<typeof signUpSchema>

export const signUpWithCredentials = async (request: SignUpRequest) => {
const result = signUpSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}

const {email, password, name} = result.data
const signUpWithCredentialsAction = async (request: SignUpRequest) => {
const {email, password, name} = signUpSchema.parse(request)

const hashedPassword = await bcrypt.hash(password, 10)

Expand All @@ -27,8 +22,7 @@ export const signUpWithCredentials = async (request: SignUpRequest) => {
})

if (user) {
// Server actions don't support throwing custom error types, so we have to use error object and handle it on client
return getApiFormError('User already exists')
throw new FormError('User already exists')
}

const data = await db
Expand All @@ -43,3 +37,7 @@ export const signUpWithCredentials = async (request: SignUpRequest) => {

return data
}

export const signUpWithCredentials = serializeServerErrors(
signUpWithCredentialsAction,
)
23 changes: 10 additions & 13 deletions src/server/actions/contest/addContest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {z} from 'zod'

import {isJudge, requireServerSession} from '@/server/utils/auth'
import {db, schema} from '@/server/db'
import {getApiZodError} from '@/lib/utils/common/error'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {
addContestSchema,
addContestSeverityWeightsSchema,
} from '@/server/utils/validations/schemas'
import {ServerError} from '@/lib/types/error'

const addContestRequestSchema = z.object({
contest: addContestSchema,
Expand All @@ -18,27 +19,21 @@ const addContestRequestSchema = z.object({

export type AddContestRequest = z.infer<typeof addContestRequestSchema>

export const addContest = async (request: AddContestRequest) => {
const addContestAction = async (request: AddContestRequest) => {
const session = await requireServerSession()

if (isJudge(session)) {
throw new Error("Judges can't create contests.")
throw new ServerError("Judges can't create contests.")
}

const result = addContestRequestSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}

const {contest, customWeights} = result.data
const {contest, customWeights} = addContestRequestSchema.parse(request)

if (isPast(contest.startDate)) {
throw new Error('Contest start date must be in the future.')
throw new ServerError('Contest start date must be in the future.')
}

if (isAfter(contest.startDate, contest.endDate)) {
throw new Error('Contest start date must be before end date.')
throw new ServerError('Contest start date must be before end date.')
}

return db.transaction(async (tx) => {
Expand All @@ -51,7 +46,7 @@ export const addContest = async (request: AddContestRequest) => {
.returning()

if (!insertedContest[0]) {
throw new Error('Failed to create contest')
throw new ServerError('Failed to create contest.')
}

await tx.insert(schema.contestSeverityWeights).values({
Expand All @@ -62,3 +57,5 @@ export const addContest = async (request: AddContestRequest) => {
return insertedContest
})
}

export const addContest = serializeServerErrors(addContestAction)
19 changes: 8 additions & 11 deletions src/server/actions/contest/addKnownIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {z} from 'zod'
import {db, schema} from '@/server/db'
import {insertKnownIssueSchema} from '@/server/db/schema/knownIssue'
import {requireServerSession} from '@/server/utils/auth'
import {getApiZodError} from '@/lib/utils/common/error'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {ServerError} from '@/lib/types/error'

const addKnownIssueSchema = insertKnownIssueSchema
.omit({contestId: true})
Expand All @@ -19,28 +20,22 @@ const addKnownIssuesSchema = z.object({
export type AddKnownIssue = z.infer<typeof addKnownIssueSchema>
export type AddKnownIssuesRequest = z.infer<typeof addKnownIssuesSchema>

export const addKnownIssues = async (request: AddKnownIssuesRequest) => {
const addKnownIssuesAction = async (request: AddKnownIssuesRequest) => {
const session = await requireServerSession()

const result = addKnownIssuesSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}

const {contestId, knownIssues} = result.data
const {contestId, knownIssues} = addKnownIssuesSchema.parse(request)

const contest = await db.query.contests.findFirst({
columns: {authorId: true},
where: (contests, {eq}) => eq(contests.id, contestId),
})

if (!contest) {
throw new Error('Contest not found.')
throw new ServerError('Contest not found.')
}

if (contest.authorId !== session.user.id) {
throw new Error('Only contest authors can add known issues.')
throw new ServerError('Only contest authors can add known issues.')
}

const knownIssuesToInsert = knownIssues.map((knownIssue) => ({
Expand All @@ -50,3 +45,5 @@ export const addKnownIssues = async (request: AddKnownIssuesRequest) => {

return db.insert(schema.knownIssues).values(knownIssuesToInsert).returning()
}

export const addKnownIssues = serializeServerErrors(addKnownIssuesAction)
21 changes: 10 additions & 11 deletions src/server/actions/contest/approveOrRejectContest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {z} from 'zod'
import {db, schema} from '@/server/db'
import {ContestStatus} from '@/server/db/models'
import {requireJudgeAuth} from '@/server/utils/auth'
import {getApiZodError} from '@/lib/utils/common/error'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {ServerError} from '@/lib/types/error'

const approveOrRejectContestSchema = z
.object({
Expand All @@ -19,30 +20,24 @@ export type ApproveOrRejectContestRequest = z.infer<
typeof approveOrRejectContestSchema
>

export const approveOrRejectContest = async (
const approveOrRejectContestAction = async (
request: ApproveOrRejectContestRequest,
) => {
await requireJudgeAuth()

const result = approveOrRejectContestSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}

const {contestId, newStatus} = result.data
const {contestId, newStatus} = approveOrRejectContestSchema.parse(request)

const contest = await db.query.contests.findFirst({
columns: {status: true},
where: (contests, {eq}) => eq(contests.id, contestId),
})

if (!contest) {
throw new Error('Contest not found.')
throw new ServerError('Contest not found.')
}

if (contest.status !== ContestStatus.PENDING) {
throw new Error('Only pending contests can be approved/rejected.')
throw new ServerError('Only pending contests can be approved/rejected.')
}

return db
Expand All @@ -51,3 +46,7 @@ export const approveOrRejectContest = async (
.where(eq(schema.contests.id, contestId))
.returning()
}

export const approveOrRejectContest = serializeServerErrors(
approveOrRejectContestAction,
)
6 changes: 5 additions & 1 deletion src/server/actions/contest/deleteContest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import {eq} from 'drizzle-orm'

import {db, schema} from '@/server/db'
import {requireEditableContest} from '@/server/utils/validations/contest'
import {serializeServerErrors} from '@/lib/utils/common/error'

export type DeleteContestResponse = Awaited<ReturnType<typeof deleteContest>>

export const deleteContest = async (contestId: string) => {
const deleteContestAction = async (contestId: string) => {
await requireEditableContest(contestId)

return db
.delete(schema.contests)
.where(eq(schema.contests.id, contestId))
.returning()
}

export const deleteContest = serializeServerErrors(deleteContestAction)
12 changes: 8 additions & 4 deletions src/server/actions/contest/deleteKnownIssue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {eq} from 'drizzle-orm'

import {db, schema} from '@/server/db'
import {requireServerSession} from '@/server/utils/auth'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {ServerError} from '@/lib/types/error'

export type DeleteKnownIssueResponse = Awaited<
ReturnType<typeof deleteKnownIssue>
>

export const deleteKnownIssue = async (knownIssueId: string) => {
const deleteKnownIssueAction = async (knownIssueId: string) => {
const session = await requireServerSession()

const knownIssue = await db.query.knownIssues.findFirst({
Expand All @@ -23,19 +25,21 @@ export const deleteKnownIssue = async (knownIssueId: string) => {
})

if (!knownIssue) {
throw new Error('Contest known issue not found.')
throw new ServerError('Contest known issue not found.')
}

if (knownIssue.contest.authorId !== session.user.id) {
throw new Error('Only authors can delete their known issues.')
throw new ServerError('Only authors can delete their known issues.')
}

if (isPast(knownIssue.contest.startDate)) {
throw new Error('Contest has started.')
throw new ServerError('Contest has started.')
}

return db
.delete(schema.knownIssues)
.where(eq(schema.knownIssues.id, knownIssueId))
.returning()
}

export const deleteKnownIssue = serializeServerErrors(deleteKnownIssueAction)
22 changes: 10 additions & 12 deletions src/server/actions/contest/editContest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {isAfter, isPast} from 'date-fns'

import {db, schema} from '@/server/db'
import {requireEditableContest} from '@/server/utils/validations/contest'
import {getApiZodError} from '@/lib/utils/common/error'
import {serializeServerErrors} from '@/lib/utils/common/error'
import {
addContestSchema,
addContestSeverityWeightsSchema,
} from '@/server/utils/validations/schemas'
import {ServerError} from '@/lib/types/error'

const editContestSchema = addContestSchema.partial().required({id: true})
const editContestSeverityWeightsSchema =
Expand All @@ -20,28 +21,23 @@ const editContestRequestSchema = z.object({
contest: editContestSchema,
customWeights: editContestSeverityWeightsSchema,
})
export type EditContestRequest = z.infer<typeof editContestRequestSchema>

export const editContest = async (request: EditContestRequest) => {
const result = editContestRequestSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}
export type EditContestRequest = z.infer<typeof editContestRequestSchema>

const {contest, customWeights} = result.data
const editContestAction = async (request: EditContestRequest) => {
const {contest, customWeights} = editContestRequestSchema.parse(request)

const existingContest = await requireEditableContest(contest.id)

const updatedStartDate = contest.startDate ?? existingContest.startDate
const updatedEndDate = contest.endDate ?? existingContest.endDate

if (isPast(updatedStartDate)) {
throw new Error('Contest start date must be in the future.')
throw new ServerError('Contest start date must be in the future.')
}

if (isAfter(updatedStartDate, updatedEndDate)) {
throw new Error('Contest start date must be before end date.')
throw new ServerError('Contest start date must be before end date.')
}

return db.transaction(async (tx) => {
Expand All @@ -52,7 +48,7 @@ export const editContest = async (request: EditContestRequest) => {
.returning()

if (!updatedContest[0]) {
throw new Error('Failed to update contest')
throw new ServerError('Failed to update contest.')
}

await tx
Expand All @@ -63,3 +59,5 @@ export const editContest = async (request: EditContestRequest) => {
return updatedContest
})
}

export const editContest = serializeServerErrors(editContestAction)

0 comments on commit c929e74

Please sign in to comment.