1
Fork 0
mirror of https://github.com/Steffo99/festa.git synced 2024-10-16 15:07:27 +00:00

idk i sleep

This commit is contained in:
Steffo 2022-05-30 05:19:49 +02:00
parent 845ada57fe
commit 14308da110
Signed by: steffo
GPG key ID: 6965406171929D01
6 changed files with 295 additions and 6 deletions

View file

@ -1,7 +1,7 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { LoginContext } from '../contexts/login'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import defaultPostcard from "../public/postcards/adi-goldstein-Hli3R6LKibo-unsplash.jpg"
import { Postcard } from '../components/Postcard'
import { PostcardContext } from '../contexts/postcard'

View file

@ -0,0 +1,22 @@
import { prisma } from "../../../utils/prismaClient";
import { NextApiRequest, NextApiResponse } from "next";
import { ApiResult } from "../../../types/api";
import { restInPeace } from "../../../utils/restInPeace";
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResult<Event | Event[]>>) {
restInPeace(req, res, {
model: prisma.event,
isList: false,
whereList: {},
whereDetail: {
slug: req.query.slug,
},
create: {
slug: req.query.slug,
// TODO
},
update: {
// TODO
},
})
}

View file

@ -3,12 +3,13 @@ import { prisma } from "../../../utils/prismaClient"
import { TelegramUserDataClass } from "../../../utils/TelegramUserDataClass"
import { default as cryptoRandomString } from "crypto-random-string"
import { ApiResult } from "../../../types/api"
import { Token, User } from "@prisma/client"
import { FestaLoginData } from "../../../types/user"
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResult<FestaLoginData>>) {
switch (req.method) {
case "OPTIONS":
return res.status(200).send("")
case "POST":
switch(req.query.provider) {
case "telegram":

View file

@ -26,6 +26,8 @@ model User {
tokens Token[]
/// The Telegram accounts associated with this user.
accountsTelegram AccountTelegram[]
/// The events created by this user.
eventsCreated Event[]
}
/// A possible powerLevel value for an {@link User}.
@ -39,9 +41,9 @@ enum PowerLevel {
/// A container for user data associated with a single [Telegram](https://telegram.org/).
model AccountTelegram {
/// The id of the {@link User} associated with this account.
userId String @db.Uuid
userId String @db.Uuid
/// The {@link User} associated with this account.
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id])
/// The Telegram id of the account.
telegramId Int @id
/// The Telegram first name of the account. Always present.
@ -59,7 +61,7 @@ model AccountTelegram {
updatedAt DateTime @updatedAt
}
/// A token that can be used to authenticate to the API as an {@link User}.
/// An entity that can be used to authenticate to the API as an {@link User}.
model Token {
/// The id of the user that the token allows to login as.
userId String @db.Uuid
@ -70,3 +72,15 @@ model Token {
/// The datetime after which the token should cease to be valid for authentication.
expiresAt DateTime
}
/// The core of the project, a single instance of people gathering in a certain place at a certain time.
model Event {
/// An unique url-safe string identifying the event, and allowing it to be accessed at `/events/SLUG`.
slug String @id
/// The id of the {@link User} who created the event.
creatorId String @db.Uuid
/// The {@link User} who created the event.
creator User @relation(fields: [creatorId], references: [id])
/// The name of the event.
name String
}

View file

@ -2,4 +2,4 @@ export type ApiError = {
error: string
}
export type ApiResult<T> = ApiError | T
export type ApiResult<T> = ApiError | T | T[] | ""

252
utils/restInPeace.ts Normal file
View file

@ -0,0 +1,252 @@
/**
* 3 AM coding
*/
import { NextApiRequest, NextApiResponse } from "next";
import { ApiError, ApiResult } from "../types/api";
type RestInOptions<T> = {
/**
* Prisma delegate to operate on.
*/
model: any,
/**
* What kind of request is being performed: `true` for list, `false` for detail.
*/
isList: boolean
/**
* Where clause for Prisma queries about multiple objects.
*
* Cannot be set together with `whereDetail`.
*/
whereList: object,
/**
* Where clause for Prisma queries about a single object.
*
* Cannot be set together with `whereList`.
*/
whereDetail: object,
/**
* The same as Prisma's `create`.
*/
create: any,
/**
* The same as Prisma's `update`.
*/
update: any,
/**
* Operations not allowed.
*/
disallow?: {
head?: boolean,
options?: boolean,
retrieve?: boolean,
list?: boolean,
create?: boolean,
upsert?: boolean,
update?: boolean,
destroy?: boolean,
}
/**
* Hooks ran after a specific operation is completed.
*
* If a {@link BreakYourBones} is thrown, it will be caught and returned to the user.
*
* ```
* throw new BreakYourBones(403, {error: "Not allowed"})
* ```
*/
hooks?: {
head?: () => T,
options?: () => T,
retrieve?: (obj: T) => T,
list?: (obj: T[]) => T[],
create?: (obj: T) => T,
upsert?: (obj: T) => T,
update?: (obj: T) => T,
destroy?: () => T,
}
}
/**
* Handle an API route in a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer)ful way.
*/
export function restInPeace<T>(req: NextApiRequest, res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
// Ensure the wheres are set correctly
if (options.whereList && options.whereDetail) {
return res.status(500).json({ error: "Request is being handled as both a list operation and a detail operation" })
}
else if (!(options.whereList || options.whereDetail)) {
return res.status(500).json({ error: "Request is not being handled as any kind of operation" })
}
// Handle HEAD by returning an empty body
else if (req.method === "HEAD") {
return restInHead(res, options)
}
// Same thing for OPTIONS, but beware of weird CORS things!
else if (req.method === "OPTIONS") {
return restInOptions(res, options)
}
// GET can be both "list" and "retrieve"
else if (req.method === "GET") {
return options.isList ? restInList(res, options) : restInRetrieve(res, options)
}
// POST is always "create"
else if (req.method === "POST") {
return options.isList ? noRestForTheWicked(res) : restInCreate(res, options)
}
// PUT is always "upsert"
else if (req.method === "PUT") {
return options.isList ? noRestForTheWicked(res) : restInUpsert(res, options)
}
// PATCH is always "update"
else if (req.method === "PATCH") {
return options.isList ? noRestForTheWicked(res) : restInUpdate(res, options)
}
// DELETE is always "destroy"
else if (req.method === "DELETE") {
return options.isList ? noRestForTheWicked(res) : restInDestroy(res, options)
}
// What kind of weird HTTP methods are you using?!
else {
return noRestForTheWicked(res)
}
}
/**
* @returns Method not allowed.
*/
function noRestForTheWicked(res: NextApiResponse) {
return res.status(405).json({ error: "Method not allowed" })
}
/**
* Error which interrupts the regular flow of a hook to return something different.
*
* Caught by {@link theButcher}.
*/
export class BreakYourBones<AT> {
status: number
response: AT
constructor(status: number, response: AT) {
this.status = status
this.response = response
}
}
/**
* Handle a {@link restInPeace} hook, catching possible {@link BreakYourBones}.
*/
function theButcher<T>(obj: any, res: NextApiResponse, options: RestInOptions<T>, method: keyof RestInOptions<T>["hooks"]) {
try {
var mutated = options?.hooks?.[method]?.(obj) ?? obj
}
catch (e) {
if (e instanceof BreakYourBones) {
return res.status(e.status).json(e.response)
}
throw e
}
return mutated
}
/**
* Handle an `HEAD` HTTP request.
*/
function restInHead<T>(res: NextApiResponse<"">, options: RestInOptions<T>) {
if (options.disallow?.head) return noRestForTheWicked(res)
theButcher(undefined, res, options, "head")
return res.status(200).send("")
}
/**
* Handle an `OPTIONS` HTTP request.
*/
function restInOptions<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.options) return noRestForTheWicked(res)
theButcher(undefined, res, options, "options")
return res.status(200).send("")
}
/**
* Handle a `GET` HTTP request where a list of items is requested.
*/
function restInList<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.list) return noRestForTheWicked(res)
const objs = options.model.findMany({ where: options.whereList })
const mutatedObjs = theButcher(objs, res, options, "list")
return res.status(200).json(mutatedObjs)
}
/**
* Handle a `GET` HTTP request where a single item is requested.
*/
function restInRetrieve<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.retrieve) return noRestForTheWicked(res)
const obj = options.model.findUnique({ where: options.whereDetail })
const mutatedObj = theButcher(obj, res, options, "retrieve")
if (!obj) {
return res.status(404).json({ error: "Not found" })
}
return res.status(200).json(mutatedObj)
}
/**
* Handle a `POST` HTTP request where a single item is created.
*/
function restInCreate<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.create) return noRestForTheWicked(res)
const obj = options.model.create({ data: options.create })
const mutatedObj = theButcher(obj, res, options, "create")
return res.status(200).json(mutatedObj)
}
/**
* Handle a `PUT` HTTP request where a single item is either created or updated.
*/
function restInUpsert<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.upsert) return noRestForTheWicked(res)
const obj = options.model.upsert({
where: options.whereDetail,
create: options.create,
update: options.update,
})
const mutatedObj = theButcher(obj, res, options, "upsert")
return res.status(200).json(mutatedObj)
}
/**
* Handle a `PATCH` HTTP request where a single item is updated.
*/
function restInUpdate<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.update) return noRestForTheWicked(res)
const obj = options.model.update({
where: options.whereDetail,
data: options.update,
})
const mutatedObj = theButcher(obj, res, options, "update")
return res.status(200).json(mutatedObj)
}
/**
* Handle a `DELETE` HTTP request where a single item is destroyed.
*/
function restInDestroy<T>(res: NextApiResponse<ApiResult<T>>, options: RestInOptions<T>) {
if (options.disallow?.destroy) return noRestForTheWicked(res)
options.model.delete({
where: options.whereDetail,
})
theButcher(undefined, res, options, "destroy")
return res.status(204).send("")
}