From ce9306271d7c29adbc56f709fcaf42289b0781a2 Mon Sep 17 00:00:00 2001 From: nostrdev-com <support@nostrdev.com> Date: Wed, 2 Apr 2025 11:09:31 +0300 Subject: [PATCH] feat(nostr): added nostrEvent validation --- src/models/nostrEvent.ts | 6 ++-- src/routes/nostr.router.ts | 53 +++++++++++++++++++++++++---------- src/routes/users.router.ts | 18 ++---------- src/utils/index.ts | 1 + src/utils/route.ts | 28 ++++++++++++++++++ src/utils/validation/index.ts | 1 + src/utils/validation/nostr.ts | 24 ++++++++++++++++ src/utils/validation/user.ts | 4 +-- 8 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 src/utils/route.ts create mode 100644 src/utils/validation/nostr.ts diff --git a/src/models/nostrEvent.ts b/src/models/nostrEvent.ts index 954f0bf..351ca36 100644 --- a/src/models/nostrEvent.ts +++ b/src/models/nostrEvent.ts @@ -1,10 +1,12 @@ +import { ObjectId } from 'mongodb' export class NostrEvent { constructor( public nostrId: string, // nostr unique identifier public pubkey: string, // public key of the event creator public created_at: number, // timestamp public kind: number, // event type, e.g., review, article, comment - public tags: [string][], // array of keywords or hashtags - public content: string // text content of the event + public tags: string[][], // array of keywords or hashtags + public content: string, // text content of the event + public id?: ObjectId ) {} } diff --git a/src/routes/nostr.router.ts b/src/routes/nostr.router.ts index a9d86e5..3d46cad 100644 --- a/src/routes/nostr.router.ts +++ b/src/routes/nostr.router.ts @@ -2,6 +2,13 @@ import express, { Request, Response } from 'express' import { collections } from '../services/database.service' import { NostrEvent } from '../models' +import { + nostrEventValidation, + handleReqError, + handleReqSuccess +} from '../utils' +import { Event } from 'nostr-tools' +import Joi from 'joi' // Global Config export const nostrRouter = express.Router() @@ -26,23 +33,39 @@ nostrRouter.get('/', async (_req: Request, res: Response) => { // POST nostrRouter.post('/', async (req: Request, res: Response) => { try { - const nostrEvent = req.body as NostrEvent + const { + error, + value: event + }: { error: Joi.ValidationError | undefined; value: Event } = + nostrEventValidation(req.body) + + if (error) { + throw error.details[0].message + } + + const { id, pubkey, created_at, kind, tags, content } = event + + const events = await collections.nostrEvents + ?.find({ nostrId: id }) + .toArray() + + if (events?.length) { + throw new Error('nostr event with provided "id" exists') + } + + const nostrEvent: NostrEvent = { + nostrId: id, + pubkey, + created_at, + kind, + tags, + content + } + const result = await collections.nostrEvents?.insertOne(nostrEvent) - if (result) { - res - .status(201) - .send( - `Successfully created a new nostrEvent with id ${result.insertedId}` - ) - } else { - res.status(500).send('Failed to create a new nostrEvent.') - } + handleReqSuccess(res, result, 'nostrEvent') } catch (error: unknown) { - console.error(error) - - if (error instanceof Error) { - res.status(400).send(error.message) - } + handleReqError(res, error) } }) diff --git a/src/routes/users.router.ts b/src/routes/users.router.ts index c62a48d..a203b37 100644 --- a/src/routes/users.router.ts +++ b/src/routes/users.router.ts @@ -2,7 +2,7 @@ import express, { Request, Response } from 'express' import { collections } from '../services/database.service' import { User } from '../models' -import { userValidation } from '../utils' +import { userValidation, handleReqError, handleReqSuccess } from '../utils' import Joi from 'joi' // Global Config @@ -59,20 +59,8 @@ usersRouter.post('/', async (req: Request, res: Response) => { const result = await collections.users?.insertOne(newUser) - if (result) { - res - .status(201) - .send(`Successfully created a new user with id ${result.insertedId}`) - } else { - res.status(500).send('Failed to create a new user.') - } + handleReqSuccess(res, result, 'user') } catch (error) { - console.error(error) - - if (error instanceof Error) { - res.status(400).send(error.message) - } else if (typeof error === 'string') { - res.status(400).send(error) - } + handleReqError(res, error) } }) diff --git a/src/utils/index.ts b/src/utils/index.ts index b46597d..086db4e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './validation' export * from './nostr' +export * from './route' diff --git a/src/utils/route.ts b/src/utils/route.ts new file mode 100644 index 0000000..b5a358e --- /dev/null +++ b/src/utils/route.ts @@ -0,0 +1,28 @@ +import { Response } from 'express' +import { InsertOneResult } from 'mongodb' + +export const handleReqError = (res: Response, error: unknown) => { + console.error(error) + + if (error instanceof Error) { + res.status(400).send(error.message) + } else if (typeof error === 'string') { + res.status(400).send(error) + } +} + +export const handleReqSuccess = ( + res: Response, + result: InsertOneResult<Document> | undefined, + itemName: string +) => { + if (result) { + res + .status(201) + .send( + `Successfully created a new ${itemName} with id ${result.insertedId}` + ) + } else { + res.status(500).send(`Failed to create a new ${itemName}.`) + } +} diff --git a/src/utils/validation/index.ts b/src/utils/validation/index.ts index c3a9c65..7a791a3 100644 --- a/src/utils/validation/index.ts +++ b/src/utils/validation/index.ts @@ -1 +1,2 @@ export * from './user' +export * from './nostr' diff --git a/src/utils/validation/nostr.ts b/src/utils/validation/nostr.ts new file mode 100644 index 0000000..3cd9ab9 --- /dev/null +++ b/src/utils/validation/nostr.ts @@ -0,0 +1,24 @@ +import Joi from 'joi' +import { npubToHex, validateHex } from '../nostr' + +export const nostrEventValidation = (data: unknown): Joi.ValidationResult => + Joi.object({ + id: Joi.string().required(), + kind: Joi.number().required(), + content: Joi.string().required(), + tags: Joi.array().items(Joi.array().items(Joi.string())), + created_at: Joi.number().required(), + pubkey: Joi.string() + .custom((value, helper) => { + const hex = npubToHex(value as string) + + if (!hex || !validateHex(hex)) { + return helper.message({ + custom: Joi.expression('"pubkey" contains an invalid value') + }) + } + + return value + }) + .required() + }).validate(data) diff --git a/src/utils/validation/user.ts b/src/utils/validation/user.ts index e022e7c..ddef1f7 100644 --- a/src/utils/validation/user.ts +++ b/src/utils/validation/user.ts @@ -16,11 +16,11 @@ const npubValidation = (value: unknown, helper: Joi.CustomHelpers<unknown>) => { export const userValidation = (data: unknown): Joi.ValidationResult => Joi.object({ - name: Joi.string().not('').required(), + name: Joi.string().required(), npub: Joi.alternatives() .try( Joi.array().items(Joi.string().custom(npubValidation)), - Joi.string().not('').custom(npubValidation) + Joi.string().custom(npubValidation) ) .required(), role: Joi.string()