mirror of
https://github.com/michivonah/themepark-assistant.git
synced 2025-12-22 14:06:29 +01:00
implement endpoints for list, create & remove of notification methods; move owner check of notification method into own file
This commit is contained in:
parent
60a75f7894
commit
0b11cccf33
8 changed files with 179 additions and 50 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
# themepark-assistant
|
# themepark-assistant
|
||||||
A tool for improving your trips to themeparks - once developed
|
A tool for improving your trips to themeparks - once developed
|
||||||
|
|
||||||
|
> HINT: The tool is currently under development. The API endpoints are subject to change at any time. Use with caution.
|
||||||
|
|
||||||
## Repo structure
|
## Repo structure
|
||||||
- /api: API implementation
|
- /api: API implementation
|
||||||
- ./: config files
|
- ./: config files
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ export const notificationMethod = sqliteTable('notification_method', {
|
||||||
|
|
||||||
export const notificationProvider = sqliteTable('notification_provider', {
|
export const notificationProvider = sqliteTable('notification_provider', {
|
||||||
id: integer().primaryKey({ autoIncrement: true }),
|
id: integer().primaryKey({ autoIncrement: true }),
|
||||||
name: text().notNull().unique()
|
name: text().notNull().unique(),
|
||||||
|
isActive: integer({ mode: 'boolean' }).notNull().default(false),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const themepark = sqliteTable('themepark', {
|
export const themepark = sqliteTable('themepark', {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,12 @@ export class MissingMailError extends HTTPException{
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MissingParameter extends HTTPException{
|
export class MissingParameter extends HTTPException{
|
||||||
constructor(paramName: string){
|
constructor(paramName?: string){
|
||||||
super(400, { message: `Request parameter '${paramName}' missing` })
|
super(400, { message:
|
||||||
|
paramName
|
||||||
|
? `Request parameter '${paramName}' missing`
|
||||||
|
: 'Request parameter missing'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { authHandler, initAuthConfig, verifyAuth } from '@hono/auth-js'
|
||||||
import { getUser } from './lib/user-auth'
|
import { getUser } from './lib/user-auth'
|
||||||
import GitHub from '@auth/core/providers/github'
|
import GitHub from '@auth/core/providers/github'
|
||||||
import attraction from './routes/attraction'
|
import attraction from './routes/attraction'
|
||||||
import notification from './routes/notification'
|
import notification from './routes/notification-method'
|
||||||
import logbook from './routes/logbook'
|
import logbook from './routes/logbook'
|
||||||
import themepark from './routes/themepark'
|
import themepark from './routes/themepark'
|
||||||
import cronRouter from './jobs/cron'
|
import cronRouter from './jobs/cron'
|
||||||
|
|
@ -34,7 +34,7 @@ app.get('/protected', async (c) => {
|
||||||
|
|
||||||
// define routes & export app
|
// define routes & export app
|
||||||
app.route('/attraction', attraction)
|
app.route('/attraction', attraction)
|
||||||
app.route('/notification', notification)
|
app.route('/notification-method', notification)
|
||||||
app.route('/logbook', logbook)
|
app.route('/logbook', logbook)
|
||||||
app.route('/themepark', themepark)
|
app.route('/themepark', themepark)
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
30
api/src/lib/check-notification-method-owner.ts
Normal file
30
api/src/lib/check-notification-method-owner.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { getDbContext, getDbEnv } from '../db/client'
|
||||||
|
import { notificationMethod } from '../db/schema';
|
||||||
|
import { NotificationMethodSelect } from '../types/notification-method'
|
||||||
|
import { DatabaseError, InvalidParameter } from '../errors'
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
type NotificationMethodUser = Pick<NotificationMethodSelect, "userId">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a specified user is the owner of a notification method and returns the owner if valid
|
||||||
|
* @param db DB connection (already defined as variable/const)
|
||||||
|
* @param methodId notificationMethodId to check
|
||||||
|
* @param userId User to check wheter it is the owner
|
||||||
|
* @returns Object with the owners userId
|
||||||
|
*/
|
||||||
|
export async function getNotificationMethodOwner(db: ReturnType<typeof getDbContext | typeof getDbEnv>, methodId: number, userId: number): Promise<NotificationMethodUser>{
|
||||||
|
try{
|
||||||
|
const method = await db.select({
|
||||||
|
userId: notificationMethod.userId
|
||||||
|
}).from(notificationMethod)
|
||||||
|
.where(eq(notificationMethod.id, methodId)).get();
|
||||||
|
|
||||||
|
if(!method || method.userId !== userId) throw new InvalidParameter('notificationMethodId');
|
||||||
|
else return method;
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
if(e instanceof InvalidParameter) throw e;
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { Hono, Context } from 'hono'
|
import { Hono, Context } from 'hono'
|
||||||
import { getDbContext } from '../db/client'
|
import { getDbContext } from '../db/client'
|
||||||
import { attractionNotification, notificationMethod } from '../db/schema'
|
import { attractionNotification } from '../db/schema'
|
||||||
import { and, eq } from 'drizzle-orm'
|
import { and, eq } from 'drizzle-orm'
|
||||||
import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
||||||
import { getUser } from '../lib/user-auth'
|
import { getUser } from '../lib/user-auth'
|
||||||
import { Message } from '../types/response'
|
import { Message } from '../types/response'
|
||||||
import { NotificationMethodSelect } from '../types/notification-method'
|
import { getNotificationMethodOwner } from '../lib/check-notification-method-owner'
|
||||||
|
|
||||||
type NotificationMethodUser = Pick<NotificationMethodSelect, "userId">;
|
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
|
|
@ -22,29 +20,6 @@ function getNotificationMethodId(c: Context): number | undefined{
|
||||||
return parseInt(str);
|
return parseInt(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a specified user is the owner of a notification method and returns the owner if valid
|
|
||||||
* @param db DB connection (already defined as variable/const)
|
|
||||||
* @param methodId notificationMethodId to check
|
|
||||||
* @param userId User to check wheter it is the owner
|
|
||||||
* @returns Object with the owners userId
|
|
||||||
*/
|
|
||||||
async function getNotificationMethodOwner(db: ReturnType<typeof getDbContext>, methodId: number, userId: number): Promise<NotificationMethodUser>{
|
|
||||||
try{
|
|
||||||
const method = await db.select({
|
|
||||||
userId: notificationMethod.userId
|
|
||||||
}).from(notificationMethod)
|
|
||||||
.where(eq(notificationMethod.id, methodId)).get();
|
|
||||||
|
|
||||||
if(!method || method.userId !== userId) throw new InvalidParameter('notificationMethodId');
|
|
||||||
else return method;
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
if(e instanceof InvalidParameter) throw e;
|
|
||||||
throw new DatabaseError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if drizzle db error has a message cause
|
* Checks if drizzle db error has a message cause
|
||||||
* @param e Thrown error
|
* @param e Thrown error
|
||||||
|
|
|
||||||
135
api/src/routes/notification-method.ts
Normal file
135
api/src/routes/notification-method.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { getDbContext } from '../db/client'
|
||||||
|
import { notificationMethod, notificationProvider } from '../db/schema'
|
||||||
|
import { eq, and } from 'drizzle-orm'
|
||||||
|
import { getUser } from '../lib/user-auth'
|
||||||
|
import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
||||||
|
import { Message } from '../types/response'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets you the id of a notificaton provider by name
|
||||||
|
* @param db DB connection (as const)
|
||||||
|
* @param name Name of the notification provider
|
||||||
|
* @returns Id of the specified notification provider's name (undefined if not exists)
|
||||||
|
*/
|
||||||
|
async function getProviderId(db: ReturnType<typeof getDbContext>, name: string){
|
||||||
|
try{
|
||||||
|
const provider = await db.select({
|
||||||
|
id: notificationProvider.id
|
||||||
|
}).from(notificationProvider)
|
||||||
|
.where(eq(notificationProvider.name, name)).get();
|
||||||
|
|
||||||
|
return provider?.id;
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
throw new InvalidParameter('provider');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a list of all notification methods a user owns */
|
||||||
|
app.get('/list', async (c) => {
|
||||||
|
const db = getDbContext(c);
|
||||||
|
const user = await getUser(c);
|
||||||
|
|
||||||
|
try{
|
||||||
|
const methods = await db.select({
|
||||||
|
id: notificationMethod.id,
|
||||||
|
webhook: notificationMethod.webhookUrl,
|
||||||
|
name: notificationMethod.shownName,
|
||||||
|
provider: notificationProvider.name
|
||||||
|
}).from(notificationMethod)
|
||||||
|
.where(eq(notificationMethod.userId, user.id))
|
||||||
|
.innerJoin(notificationProvider, eq(notificationMethod.notificationProviderId, notificationProvider.id));
|
||||||
|
|
||||||
|
return c.json(methods);
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Lists all available notification providers */
|
||||||
|
app.get('/list-providers', async (c) => {
|
||||||
|
const db = getDbContext(c);
|
||||||
|
|
||||||
|
try{
|
||||||
|
const providers = await db.selectDistinct({
|
||||||
|
name: notificationProvider.name
|
||||||
|
}).from(notificationProvider)
|
||||||
|
.where(eq(notificationProvider.isActive, true));
|
||||||
|
|
||||||
|
return c.json(providers);
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Creates a new notification method from url, name & provider */
|
||||||
|
app.post('/add-method', async (c) => {
|
||||||
|
const db = getDbContext(c);
|
||||||
|
const user = await getUser(c);
|
||||||
|
|
||||||
|
const { url, name, provider } = c.req.query();
|
||||||
|
|
||||||
|
if(!url || !name || !provider) throw new MissingParameter();
|
||||||
|
|
||||||
|
const providerId = await getProviderId(db, provider);
|
||||||
|
if(!providerId) throw new InvalidParameter('provider');
|
||||||
|
|
||||||
|
try{
|
||||||
|
const newMethod = await db.insert(notificationMethod).values({
|
||||||
|
webhookUrl: url,
|
||||||
|
shownName: name,
|
||||||
|
userId: user.id,
|
||||||
|
notificationProviderId: providerId
|
||||||
|
}).returning().onConflictDoNothing().get();
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
newMethod
|
||||||
|
? new Message('Successfull created new notification method.', {
|
||||||
|
id: newMethod.id,
|
||||||
|
webhook: newMethod.webhookUrl,
|
||||||
|
name: newMethod.shownName
|
||||||
|
})
|
||||||
|
: new Message('Notification method with this URL already exists. No changes made.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Removes a existing notification method by id (has to be owned by the current user) */
|
||||||
|
app.delete('/remove-method/:id', async (c) => {
|
||||||
|
const db = getDbContext(c);
|
||||||
|
const user = await getUser(c);
|
||||||
|
const methodId = parseInt(c.req.param('id'));
|
||||||
|
|
||||||
|
if(!methodId) throw new InvalidParameter('id');
|
||||||
|
|
||||||
|
try{
|
||||||
|
const res = await db.delete(notificationMethod).where(
|
||||||
|
and(
|
||||||
|
eq(notificationMethod.id, methodId),
|
||||||
|
eq(notificationMethod.userId, user.id)
|
||||||
|
)
|
||||||
|
).returning();
|
||||||
|
|
||||||
|
return c.json(new Message(
|
||||||
|
res.length > 0
|
||||||
|
? `Notification method ${methodId} was removed.`
|
||||||
|
: `No matching notification method with id ${methodId}. No changes made.`,
|
||||||
|
{
|
||||||
|
notificationMethodId: methodId
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { Hono } from 'hono'
|
|
||||||
import { getDbContext } from '../db/client'
|
|
||||||
import { notificationMethod, user, themepark } from '../db/schema'
|
|
||||||
|
|
||||||
const app = new Hono()
|
|
||||||
|
|
||||||
app.get('/list', async (c) => {
|
|
||||||
const db = getDbContext(c)
|
|
||||||
await db.insert(user).values({ username: 'notification'});
|
|
||||||
//await db.insert(themepark).values({ name: 'Test', countrycode: 'CH'});
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
message: 'List all notification methods'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default app
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue