mirror of
https://github.com/michivonah/themepark-assistant.git
synced 2025-12-22 14:06:29 +01:00
refactor validation by using zod + caching for more endpoints
This commit is contained in:
parent
65f34d4d45
commit
1729766d06
7 changed files with 59 additions and 38 deletions
|
|
@ -1,9 +1,17 @@
|
|||
import { cache } from 'hono/cache'
|
||||
|
||||
/**
|
||||
* Cache unit to use for multiple endpoints as needed
|
||||
* Cache unit to use for multiple endpoints as needed (TTL: 86400s)
|
||||
*/
|
||||
export const responseCache = cache({
|
||||
cacheName: 'themepark-assistant',
|
||||
cacheControl: 'max-age=86400'
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache for dynamic data (TTL: 30s)
|
||||
*/
|
||||
export const dynamicCache = cache({
|
||||
cacheName: 'themepark-assistant-dynamic',
|
||||
cacheControl: 'max-age=30s'
|
||||
});
|
||||
|
|
@ -8,8 +8,23 @@ import { InvalidParameter } from '../errors'
|
|||
* @param schema Zod Validation scheme (docs: https://zod.dev/api)
|
||||
* @returns zValidator for running the validation
|
||||
*/
|
||||
export default function httpZValidator<T extends z.ZodTypeAny>(type: 'query' | 'json' | 'param' = 'query', schema: T){
|
||||
export function httpZValidator<T extends z.ZodTypeAny>(type: 'query' | 'json' | 'param' = 'query', schema: T){
|
||||
return zValidator(type, schema, (result, c) => {
|
||||
if(!result.success) throw new InvalidParameter();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Predefined validators
|
||||
/**
|
||||
* Validates if id is a number (using zod)
|
||||
*/
|
||||
export const idValidator = httpZValidator('param', z.strictObject({
|
||||
id: z.coerce.number()
|
||||
}));
|
||||
|
||||
/**
|
||||
* Validates if notificationMethodId is number (using zod)
|
||||
*/
|
||||
export const notificationMethodIdValidator = httpZValidator('query', z.strictObject({
|
||||
notificationMethodId: z.coerce.number()
|
||||
}));
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Hono, Context } from 'hono'
|
||||
import { Hono } from 'hono'
|
||||
import { getDbContext } from '../db/client'
|
||||
import { attractionNotification } from '../db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
|
|
@ -6,19 +6,14 @@ import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
|||
import { getUser } from '../lib/user-auth'
|
||||
import { Message } from '../types/response'
|
||||
import { getNotificationMethodOwner } from '../lib/check-notification-method-owner'
|
||||
import { httpZValidator, idValidator, notificationMethodIdValidator } from '../lib/http-z-validator'
|
||||
import * as z from 'zod'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
/**
|
||||
* Checks if request has notificationMethodId parameter
|
||||
* @param c Request context
|
||||
* @returns if available notificationMethodId, else undefined
|
||||
*/
|
||||
function getNotificationMethodId(c: Context): number | undefined{
|
||||
const str = c.req.query('notificationMethodId');
|
||||
if(!str) return undefined;
|
||||
return parseInt(str);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if drizzle db error has a message cause
|
||||
|
|
@ -37,13 +32,12 @@ function hasMessageCause(e: unknown): e is Error & { cause: { message: string }}
|
|||
/**
|
||||
* Subscribe to waittime notifications from a specified attraction
|
||||
*/
|
||||
app.post('/:id/subscribe', async (c) => {
|
||||
const attractionId = parseInt(c.req.param('id'));
|
||||
app.post('/:id/subscribe', idValidator, notificationMethodIdValidator, async (c) => {
|
||||
const attractionId = c.req.valid('param').id;
|
||||
const db = getDbContext(c)
|
||||
const user = await getUser(c);
|
||||
|
||||
const notificationMethodId = getNotificationMethodId(c);
|
||||
if(!notificationMethodId) throw new MissingParameter('notificationMethodId');
|
||||
const notificationMethodId = c.req.valid('query').notificationMethodId;
|
||||
|
||||
const method = await getNotificationMethodOwner(db, notificationMethodId, user.id);
|
||||
|
||||
|
|
@ -73,12 +67,12 @@ app.post('/:id/subscribe', async (c) => {
|
|||
/**
|
||||
* Unsubscribe to waittime notifications from a specified attraction
|
||||
*/
|
||||
app.post('/:id/unsubscribe', async (c) => {
|
||||
const attractionId = parseInt(c.req.param('id'));
|
||||
app.post('/:id/unsubscribe', idValidator, notificationMethodIdValidator, async (c) => {
|
||||
const attractionId = c.req.valid('param').id;
|
||||
const db = getDbContext(c)
|
||||
const user = await getUser(c);
|
||||
|
||||
const notificationMethodId = getNotificationMethodId(c);
|
||||
const notificationMethodId = c.req.valid('query').notificationMethodId;
|
||||
const methodOwner = notificationMethodId ? await getNotificationMethodOwner(db, notificationMethodId, user.id): false;
|
||||
|
||||
const queryConditions = [
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import { logbook } from '../db/schema'
|
|||
import { and, eq } from 'drizzle-orm'
|
||||
import { getUser } from '../lib/user-auth'
|
||||
import { Message } from '../types/response'
|
||||
|
||||
import { httpZValidator } from '../lib/http-z-validator'
|
||||
import * as z from 'zod'
|
||||
import httpZValidator from '../lib/http-z-validator'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { eq, and } from 'drizzle-orm'
|
|||
import { getUser } from '../lib/user-auth'
|
||||
import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
||||
import { Message } from '../types/response'
|
||||
import { responseCache, dynamicCache } from '../lib/cache'
|
||||
import { httpZValidator, idValidator } from '../lib/http-z-validator'
|
||||
import * as z from 'zod'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
|
|
@ -51,7 +54,7 @@ app.get('/list', async (c) => {
|
|||
})
|
||||
|
||||
/** Lists all available notification providers */
|
||||
app.get('/list-providers', async (c) => {
|
||||
app.get('/list-providers', responseCache, async (c) => {
|
||||
const db = getDbContext(c);
|
||||
|
||||
try{
|
||||
|
|
@ -68,21 +71,24 @@ app.get('/list-providers', async (c) => {
|
|||
})
|
||||
|
||||
/** Creates a new notification method from url, name & provider */
|
||||
app.post('/add-method', async (c) => {
|
||||
app.post('/add-method', httpZValidator('query', z.strictObject({
|
||||
url: z.string(),
|
||||
name: z.string(),
|
||||
provider: z.string()
|
||||
})),
|
||||
async (c) => {
|
||||
const db = getDbContext(c);
|
||||
const user = await getUser(c);
|
||||
|
||||
const { url, name, provider } = c.req.query();
|
||||
const params = c.req.valid('query');
|
||||
|
||||
if(!url || !name || !provider) throw new MissingParameter();
|
||||
|
||||
const providerId = await getProviderId(db, provider);
|
||||
const providerId = await getProviderId(db, params.provider);
|
||||
if(!providerId) throw new InvalidParameter('provider');
|
||||
|
||||
try{
|
||||
const newMethod = await db.insert(notificationMethod).values({
|
||||
webhookUrl: url,
|
||||
shownName: name,
|
||||
webhookUrl: params.url,
|
||||
shownName: params.name,
|
||||
userId: user.id,
|
||||
notificationProviderId: providerId
|
||||
}).returning().onConflictDoNothing().get();
|
||||
|
|
@ -103,12 +109,10 @@ app.post('/add-method', async (c) => {
|
|||
})
|
||||
|
||||
/** Removes a existing notification method by id (has to be owned by the current user) */
|
||||
app.delete('/remove-method/:id', async (c) => {
|
||||
app.delete('/remove-method/:id', idValidator, async (c) => {
|
||||
const db = getDbContext(c);
|
||||
const user = await getUser(c);
|
||||
const methodId = parseInt(c.req.param('id'));
|
||||
|
||||
if(!methodId) throw new InvalidParameter('id');
|
||||
const methodId = c.req.valid('param').id;
|
||||
|
||||
try{
|
||||
const res = await db.delete(notificationMethod).where(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { themepark, attraction } from '../db/schema'
|
|||
import { responseCache } from '../lib/cache'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { DatabaseError } from '../errors'
|
||||
import { idValidator } from '../lib/http-z-validator'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
|
|
@ -30,8 +31,8 @@ app.get('/list', responseCache, async (c) => {
|
|||
/**
|
||||
* Lists all attractions from a themepark with their id & name
|
||||
*/
|
||||
app.get('/list/:id/attraction', responseCache, async (c) => {
|
||||
const parkId = parseInt(c.req.param('id'));
|
||||
app.get('/list/:id/attraction', responseCache, idValidator, async (c) => {
|
||||
const parkId = c.req.valid('param').id;
|
||||
const db = getDbContext(c)
|
||||
|
||||
try{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { getUser } from '../lib/user-auth'
|
|||
import { user } from '../db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { Message } from '../types/response'
|
||||
import { httpZValidator } from '../lib/http-z-validator'
|
||||
import * as z from 'zod'
|
||||
import httpZValidator from '../lib/http-z-validator'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue