Skip to content

Commit

Permalink
refactor: swap HoF with pipe
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierniki committed Sep 5, 2023
1 parent 856c374 commit d7fdad1
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 131 deletions.
22 changes: 22 additions & 0 deletions app/api/algolia-webhook/httpError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextResponse } from "next/server"

export class HttpError extends Error {
status: number
tag: string
readonly isHttpError = true

constructor(tag: string, status: number) {
super()
this.tag = tag
this.status = status
}
}

export function isHttpError(error: unknown): error is HttpError {
return (error as HttpError).isHttpError
}

export function errorToNextResponse(error: unknown) {
if (!isHttpError(error)) return NextResponse.json({ message: "UnexpectedError" }, { status: 500 })
return NextResponse.json({ message: error.tag }, { status: error.status })
}
46 changes: 25 additions & 21 deletions app/api/algolia-webhook/publish/route.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
import algolia from "algoliasearch"
import { env } from "env.mjs"
import { NextResponse } from "next/server"
import { NextRequest, NextResponse } from "next/server"
import { pipe } from "utils/pipe"
import { slateToText } from "utils/slateToText"
import { z } from "zod"
import { withValidSignature } from "../withValidSignature"
import { NextRequestWithValidBody, withBodySchema } from "../withBodySchema"
import { errorToNextResponse } from "../httpError"
import { NextRequestWithValidBody, validateBody } from "../validateBody"
import { validateSignature } from "../validateSignature"

const client = algolia(env.ALGOLIA_API_ID, env.ALGOLIA_API_KEY)

async function handleAlgoliaWebhook(req: NextRequestWithValidBody<z.infer<typeof bodySchema>>) {
try {
const article = req.validBody.data

const indexingResults = await Promise.allSettled(
article.localizations.map(async ({ locale, title, content }) => {
const index = client.initIndex(`articles-${locale}`)
await index.saveObject({
objectID: article.id,
title,
content: slateToText(content),
})
const article = req.validBody.data

return { title, locale }
const indexingResults = await Promise.allSettled(
article.localizations.map(async ({ locale, title, content }) => {
const index = client.initIndex(`articles-${locale}`)
await index.saveObject({
objectID: article.id,
title,
content: slateToText(content),
})
)

return NextResponse.json({ result: indexingResults }, { status: 201 })
} catch (err) {
return NextResponse.json({ message: "Unexpected Error" }, { status: 500 })
return { title, locale }
})
)

return NextResponse.json({ result: indexingResults }, { status: 201 })
}

export async function POST(req: NextRequest) {
try {
return await pipe(req, validateSignature, validateBody(bodySchema), handleAlgoliaWebhook)
} catch (error) {
return errorToNextResponse(error)
}
}

Expand All @@ -37,5 +43,3 @@ const bodySchema = z.object({
id: z.string(),
}),
})

export const POST = withValidSignature(withBodySchema(handleAlgoliaWebhook, bodySchema))
41 changes: 0 additions & 41 deletions app/api/algolia-webhook/route.ts

This file was deleted.

46 changes: 25 additions & 21 deletions app/api/algolia-webhook/unpublish/route.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
import algolia from "algoliasearch"
import { env } from "env.mjs"
import { NextResponse } from "next/server"
import { NextRequest, NextResponse } from "next/server"
import { pipe } from "utils/pipe"
import { slateToText } from "utils/slateToText"
import { z } from "zod"
import { withValidSignature } from "../withValidSignature"
import { NextRequestWithValidBody, withBodySchema } from "../withBodySchema"
import { errorToNextResponse } from "../httpError"
import { NextRequestWithValidBody, validateBody } from "../validateBody"
import { validateSignature } from "../validateSignature"

const client = algolia(env.ALGOLIA_API_ID, env.ALGOLIA_API_KEY)

async function handleAlgoliaWebhook(req: NextRequestWithValidBody<z.infer<typeof bodySchema>>) {
try {
const article = req.validBody.data

const indexingResults = await Promise.allSettled(
article.localizations.map(async ({ locale, title, content }) => {
const index = client.initIndex(`articles-${locale}`)
await index.saveObject({
objectID: article.id,
title,
content: slateToText(content),
})
const article = req.validBody.data

return { title, locale }
const indexingResults = await Promise.allSettled(
article.localizations.map(async ({ locale, title, content }) => {
const index = client.initIndex(`articles-${locale}`)
await index.saveObject({
objectID: article.id,
title,
content: slateToText(content),
})
)

return NextResponse.json({ result: indexingResults }, { status: 201 })
} catch (err) {
return NextResponse.json({ message: "Unexpected Error" }, { status: 500 })
return { title, locale }
})
)

return NextResponse.json({ result: indexingResults }, { status: 201 })
}

export async function POST(req: NextRequest) {
try {
return await pipe(req, validateSignature, validateBody(bodySchema), handleAlgoliaWebhook)
} catch (error) {
return errorToNextResponse(error)
}
}

Expand All @@ -37,5 +43,3 @@ const bodySchema = z.object({
id: z.string(),
}),
})

export const POST = withValidSignature(withBodySchema(handleAlgoliaWebhook, bodySchema))
23 changes: 23 additions & 0 deletions app/api/algolia-webhook/validateBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NextRequest } from "next/server"

import { HttpError } from "./httpError"
import { NextRequestWithBody, hasParsedBody } from "./validateSignature"

export const validateBody =
<T>(schema: Zod.Schema<T>) =>
async (req: NextRequest | NextRequestWithBody) => {
try {
const hasBody = hasParsedBody(req)

const parseResult = schema.safeParse(hasBody ? req.body : await req.json())
if (!parseResult.success) throw new HttpError("BadRequest", 400)

const reqWithBody: NextRequestWithValidBody<T> = Object.assign(req, { validBody: parseResult.data })

return reqWithBody
} catch {
throw new HttpError("BadRequest", 400)
}
}

export type NextRequestWithValidBody<T> = NextRequest & { validBody: T }
25 changes: 25 additions & 0 deletions app/api/algolia-webhook/validateSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextRequest } from "next/server"
import { HttpError } from "./httpError"
import { verifyWebhookSignature } from "@hygraph/utils"
import { env } from "env.mjs"

export const validateSignature = async (req: NextRequest) => {
const authHeader = req.headers.get("gcms-signature")
if (!authHeader) throw new HttpError("Unauthorized", 401)
const parsedBody = await req.json()

const isSignatureValid = verifyWebhookSignature({
body: parsedBody,
signature: authHeader,
secret: env.HYGRAPH_WEBOOK_SECRET,
})
if (!isSignatureValid) throw new HttpError("Unauthorized", 401)

const reqWithBody = Object.assign(req, { parsedBody })
return reqWithBody
}

export type NextRequestWithBody = NextRequest & { parsedBody: unknown }

export const hasParsedBody = (req: NextRequestWithBody | NextRequest): req is NextRequestWithBody =>
Boolean((req as NextRequestWithBody).body)
22 changes: 0 additions & 22 deletions app/api/algolia-webhook/withBodySchema.ts

This file was deleted.

26 changes: 0 additions & 26 deletions app/api/algolia-webhook/withValidSignature.ts

This file was deleted.

13 changes: 13 additions & 0 deletions utils/pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type Func<A, B> = (a: A) => B | Promise<B>

export async function pipe<A>(a: A): Promise<A>
export async function pipe<A, B>(a: A, ab: Func<A, B>): Promise<B>
export async function pipe<A, B, C>(a: A, ab: Func<A, B>, bc: Func<B, C>): Promise<C>
export async function pipe<A, B, C, D>(a: A, ab: Func<A, B>, bc: Func<B, C>, cd: Func<C, D>): Promise<D>

export async function pipe(...args: any[]): Promise<any> {
return await args.slice(1).reduce(async (accPromise, fn) => {
const acc = await accPromise
return fn(acc)
}, Promise.resolve(args[0]))
}

0 comments on commit d7fdad1

Please sign in to comment.