Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
DexterStorey committed Oct 3, 2024
0 parents commit 1dd168c
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Release Package

on:
push:
branches:
- main

permissions:
contents: read
packages: write
id-token: write

jobs:
release:
uses: rubriclab/package/.github/workflows/release-package.yml@main
secrets: inherit
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Dependency directories
node_modules/
dist
.DS_Store

# Output of 'npm pack'
*.tgz

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# turbo
.turbo
21 changes: 21 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [2024-10-02] [init](https://github.com/RubricLab/webhooks/commit/fa8f2f83d7cbc48865f343e7af69fd7fa8ec2d37)
# Changelog

3 changes: 3 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["@rubriclab/config/biome"]
}
7 changes: 7 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type {
WebhookEventHandler,
WebhookProvider,
WebhookEvent,
WebhookEventMap,
WebhookEventType
} from './lib/types'
36 changes: 36 additions & 0 deletions lib/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type NextRequest, NextResponse } from 'next/server'
import type { WebhookProviders } from './types'

export function webhookHandler({ webhookProviders }: { webhookProviders: WebhookProviders }) {
return async (
request: NextRequest,
{ params }: { params: { provider: keyof WebhookProviders } }
) => {
const provider = webhookProviders[params.provider]
if (!provider) {
return NextResponse.json({ error: 'Invalid provider' }, { status: 400 })
}

const payload = await request.text()
const parsedPayload = JSON.parse(payload)

try {
const isValid = await provider.verifyWebhook({
payload,
headers: request.headers
})
if (!isValid) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 403 })
}

const response = await provider.handleWebhook({
payload: parsedPayload,
headers: request.headers
})
return NextResponse.json(response.body, { status: response.statusCode })
} catch (error) {
console.error(`Error in ${provider.provider} webhook:`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
}
65 changes: 65 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { PrismaClient } from '@prisma/client'

export interface WebhookProvider<T extends WebhookEventMap> {
provider: string
enable: ({
userId,
accountId
}: {
userId: string
accountId: string
}) => Promise<void>
disable: ({
userId,
accountId
}: {
userId: string
accountId: string
}) => Promise<void>
handleWebhook: ({
payload,
headers
}: {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
payload: any
headers: Headers
}) => Promise<WebhookResponse>
verifyWebhook: ({
payload,
headers
}: {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
payload: any
headers: Headers
}) => Promise<boolean>
eventHandlers: T
}

export type WebhookProviders = {
[provider in string]?: WebhookProvider<WebhookEventMap>
}

export interface WebhookResponse {
statusCode: number
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
body: any
}

// TODO: remove string type
export type WebhookEventType = keyof WebhookEventMap | string

export interface WebhookEvent<T, D> {
type: WebhookEventType
data: D
username?: string
action?: T
}

export type WebhookEventHandler<D> = (data: D, username: string) => Promise<void>

export type WebhookEventMap = {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
[action in string]?: WebhookEventHandler<any>
}

export type DB = PrismaClient
99 changes: 99 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { DB, WebhookProviders } from './types'

export function createWebhookActions({
webhookProviders,
db
}: { webhookProviders: WebhookProviders; db: DB }) {
return {
async getWebhooksForProvider({
userId,
provider,
accountId
}: {
userId: string
provider: string
accountId: string
}) {
const webhookProvider = webhookProviders[provider]

if (!webhookProvider) {
throw new Error(`Webhook provider not found for ${provider}`)
}

const webhooks = await db.$transaction(
Object.keys(webhookProvider.eventHandlers).map(webhookKey =>
db.webhook.findUnique({
where: {
userId_provider_accountId_webhook: {
userId,
provider: webhookProvider.provider,
accountId,
webhook: webhookKey
}
}
})
)
)

return Object.keys(webhookProvider.eventHandlers).map((webhookKey, index) => {
const existingWebhook = webhooks[index]
return (
existingWebhook || {
webhook: webhookKey,
enabled: false,
config: null,
provider: webhookProvider.provider,
accountId,
userId
}
)
})
},
async toggleWebhook({
provider,
webhook,
userId,
accountId,
enable
}: {
provider: string
webhook: string

userId: string
accountId: string

enable: boolean
}) {
const webhookProvider = webhookProviders[provider]

if (!webhookProvider) {
throw new Error(`Webhook provider not found for ${provider}`)
}

if (enable) {
await webhookProvider.enable({ userId, accountId })
} else {
await webhookProvider.disable({ userId, accountId })
}

await db.webhook.upsert({
where: {
userId_provider_accountId_webhook: {
userId,
provider: webhookProvider.provider,
accountId,
webhook
}
},
create: {
userId,
provider: webhookProvider.provider,
accountId,
webhook,
enabled: enable
},
update: { enabled: enable }
})
}
}
}
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"scripts": {
"generate": "bun scripts/generate.ts",
"watch": "bun scripts/watch.ts",
"prepare": "bun x simple-git-hooks",
"bleed": "bun x npm-check-updates -u && bun i",
"clean": "rm -rf .next && rm -rf node_modules",
"format": "bun x biome format --write .",
"lint": "bun x biome check .",
"lint:fix": "bun x biome lint . --write --unsafe"
},
"name": "@rubriclab/webhooks",
"version": "0.0.1",
"main": "index.ts",
"dependencies": {
"@rubriclab/package": "*"
},
"simple-git-hooks": {
"post-commit": "bun run rubriclab-postcommit"
},
"publishConfig": {
"access": "public"
}
}

0 comments on commit 1dd168c

Please sign in to comment.