API routes now return proper Responses and basic errors are handled

slight improvements in layouting
This commit is contained in:
Alexander Bocken 2023-07-02 23:39:31 +02:00
parent ece3b3634c
commit 24ddd39f35
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
12 changed files with 349 additions and 78 deletions

View File

@ -5,6 +5,7 @@ function show_sidebar(){
const nav_el = document.querySelector("nav") const nav_el = document.querySelector("nav")
nav_el.hidden = !nav_el.hidden nav_el.hidden = !nav_el.hidden
} }
</script> </script>
<style> <style>
:global(*){ :global(*){
@ -28,13 +29,13 @@ nav[hidden]{
display:block; display:block;
} }
li{ :global(.site_header li){
list-style-type:none; list-style-type:none;
transition: 100ms; transition: 100ms;
color: white; color: white;
user-select: none; user-select: none;
} }
li>a{ :global(.site_header li>a){
text-decoration: none; text-decoration: none;
font-family: sans-serif; font-family: sans-serif;
font-size: 1.2rem; font-size: 1.2rem;
@ -43,14 +44,14 @@ li>a{
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }
li:hover, :global(.site_header li:hover),
li:focus-within :global(.site_header li:focus-within)
{ {
cursor: pointer; cursor: pointer;
color: var(--red); color: var(--red);
transform: scale(1.1,1.1); transform: scale(1.1,1.1);
} }
ul { :global(.site_header) {
padding-block: 1.5rem; padding-block: 1.5rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -104,18 +105,18 @@ ul {
transform: translateX(100%); transform: translateX(100%);
} }
ul{ :global(.site_header){
flex-direction: column; flex-direction: column;
padding-top: min(10rem, 10vh); padding-top: min(10rem, 10vh);
} }
li{ :global(.site_header li){
font-size: 4rem; font-size: 4rem;
} }
li > a{ :global(.site_header li > a){
font-size: 2rem; font-size: 2rem;
} }
li:hover, :global(.site_header li:hover),
li:focus-within{ :global(.site_header li:focus-within){
transform: unset; transform: unset;
} }
} }
@ -136,14 +137,9 @@ margin-top: auto;
<button class=nav_button on:click={show_sidebar}><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button> <button class=nav_button on:click={show_sidebar}><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button>
</div> </div>
<nav hidden> <nav hidden>
<ul> <slot name=links></slot>
<li><a href="/">Home</a></li>
<li><a href="/rezepte">Alle Rezepte</a></li>
<li><a href="/rezepte/season">In Saison</a></li>
<li><a href="/rezepte/category">Nach Kategorie</a></li>
<li><a href="/rezepte/tag">Stichwörter</a></li>
</ul>
</nav> </nav>
<slot></slot> <slot></slot>
</div> </div>

View File

@ -1,3 +1,11 @@
<h1>Welcome to SvelteKit</h1> <script>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> import Header from '$lib/components/Header.svelte'
<a href="/rezepte"> REZEPTE </a> </script>
<Header>
<ul class=site_header slot=links>
<li><a href="/rezepte">Rezepte</a></li>
<li><a href="https://bilder.bocken.org">Familienbilder</a></li>
</ul>
</Header>

View File

@ -3,23 +3,26 @@ import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db'; import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types'; import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private' import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit';
// header: use for bearer token for now // header: use for bearer token for now
// recipe json in body // recipe json in body
export const POST: RequestHandler = async ({request}) => { export const POST: RequestHandler = async ({request}) => {
let message = await request.json() let message = await request.json()
const recipe_json = message.recipe const recipe_json = message.recipe
const bearer_token = message.headers.bearer const bearer_token = message.headers.bearer
console.log("RECIPE:", recipe_json)
console.log("BEARER:", bearer_token)
if(bearer_token === BEARER_TOKEN){ if(bearer_token === BEARER_TOKEN){
console.log("PASSWORD CORRECT")
await dbConnect(); await dbConnect();
try{
await Recipe.create(recipe_json); await Recipe.create(recipe_json);
} catch(e){
throw error(400, e)
}
await dbDisconnect(); await dbDisconnect();
return {status: 200} //TODO: cleanup error throwing return new Response(JSON.stringify({msg: "Added recipe successfully"}),{
status: 200,
});
} }
else{ else{
console.log("PASSWORD INCORRECT") throw error(403, "Password incorrect")
return {status: 403}
} }
}; };

View File

@ -3,6 +3,7 @@ import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db'; import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types'; import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private' import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit';
// header: use for bearer token for now // header: use for bearer token for now
// recipe json in body // recipe json in body
export const POST: RequestHandler = async ({request}) => { export const POST: RequestHandler = async ({request}) => {
@ -10,14 +11,14 @@ export const POST: RequestHandler = async ({request}) => {
const short_name = message.old_short_name const short_name = message.old_short_name
const bearer_token = message.headers.bearer const bearer_token = message.headers.bearer
if(bearer_token === BEARER_TOKEN){ if(bearer_token === BEARER_TOKEN){
console.log("PASSWORD CORRECT")
await dbConnect(); await dbConnect();
await Recipe.findOneAndDelete({short_name: short_name}); await Recipe.findOneAndDelete({short_name: short_name});
await dbDisconnect(); await dbDisconnect();
return {status: 400} //TODO: cleanup error throwing return new Response(JSON.stringify({msg: "Deleted recipe successfully"}),{
status: 200,
});
} }
else{ else{
console.log("PASSWORD INCORRECT") throw error(403, "Password incorrect")
return {status: 403}
} }
}; }

View File

@ -15,11 +15,12 @@ export const POST: RequestHandler = async ({request}) => {
await dbConnect(); await dbConnect();
await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json); await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json);
await dbDisconnect(); await dbDisconnect();
const res = new Response(JSON.stringify({ message: "Updated Recipe successfully"}), { status: 200 }) return new Response(JSON.stringify({msg: "Edited recipe successfully"}),{
return res status: 200,
});
} }
else{ else{
console.log("INCORRECT PASSWORD")
throw error(403, "Password incorrect") throw error(403, "Password incorrect")
} }
}; };

View File

@ -6,7 +6,6 @@ import { error } from '@sveltejs/kit';
export const POST = (async ({ request }) => { export const POST = (async ({ request }) => {
const data = await request.json(); const data = await request.json();
console.log(data)
const filePath = path.join( const filePath = path.join(
process.cwd(), process.cwd(),
"static", "static",
@ -14,13 +13,13 @@ export const POST = (async ({ request }) => {
data.filename as string data.filename as string
); );
const file = data.image; const file = data.image;
console.log(data.headers)
if(data.bearer === BEARER_TOKEN){ if(data.bearer === BEARER_TOKEN){
console.log("PASSWORD CORRECT")
writeFileSync(filePath, file, 'base64'); writeFileSync(filePath, file, 'base64');
return new Response(JSON.stringify({msg: "Added image successfully"}),{
status: 200,
});
} }
else{ else{
console.log("PASSWORD INCORRECT")
throw error(403, "Password incorrect") throw error(403, "Password incorrect")
} }

View File

@ -1,20 +0,0 @@
import path from 'path'
import fs from 'fs/promises'
import { fail, redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST = (async ({ request, url}) => {
try {
const data = Object.fromEntries(await request.formData())
const filePath = path.join(
process.cwd(),
"static",
"images",
data.filename as string
);
await fs.writeFile(filePath, Buffer.from(await (data.image as Blob).arrayBuffer()))
return new Response(String({status: 200}))
} catch (err) {
throw fail(500, { err: err })
}
}) satisfies RequestHandler;

View File

@ -0,0 +1,15 @@
<script>
import Header from '$lib/components/Header.svelte'
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/">Home</a></li>
<li><a href="/rezepte">Alle Rezepte</a></li>
<li><a href="/rezepte/season">In Saison</a></li>
<li><a href="/rezepte/category">Nach Kategorie</a></li>
<li><a href="/rezepte/tag">Stichwörter</a></li>
</ul>
<slot></slot>
</Header>

View File

@ -89,12 +89,13 @@ h1{
.wrapper_wrapper{ .wrapper_wrapper{
background-color: #fbf9f3; background-color: #fbf9f3;
width: 100svw; padding-top: 10rem;
padding-top: 3rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-bottom: 3rem; margin-bottom: 3rem;
transform: translateY(-7rem);
z-index: -2;
} }
.wrapper{ .wrapper{
@ -118,7 +119,7 @@ h1{
background-color: var(--nord6); background-color: var(--nord6);
padding: 1rem 2rem; padding: 1rem 2rem;
translate: 0 1px; /*bruh*/ translate: 0 1px; /*bruh*/
z-index: -1; z-index: 1;
} }
.icon{ .icon{
position: absolute; position: absolute;
@ -185,22 +186,13 @@ h4{
</style> </style>
<!--<MultiImgWrapper wrap={data.images.length>1} class=double>
{#each data.images as img}
<img width=100% src="/images/{img.mediapath}" alt="{img.alt}">
<figure>
{#if img.caption}
<caption>{img.caption}</caption>
{/if}
</figure>
{/each}
</MultiImgWrapper>-->
<TitleImgParallax src=/images/{data.images[0].mediapath}> <TitleImgParallax src=/images/{data.images[0].mediapath}>
<div class=title> <div class=title>
<a class="icon" href='/rezepte/season/{data.season[0]}'>{data.icon}</a> <a class="icon" href='/rezepte/season/{data.season[0]}'>{data.icon}</a>
<h1>{data.name}</h1> <h1>{data.name}</h1>
{#if data.description && ! data.preamble}
<p>{data.description}</p>
{/if}
{#if data.preamble} {#if data.preamble}
<p>{data.preamble}</p> <p>{data.preamble}</p>
{/if} {/if}

View File

@ -126,7 +126,17 @@
bearer: password, bearer: password,
} }
}) })
}) });
if(res.status === 200){
const url = location.href.split('/')
url.splice(url.length -1, 1);
url.push(short_name)
location.assign(url.join('/'))
}
else{
const item = await res.json();
alert(item.message)
}
} }

View File

@ -98,6 +98,15 @@
}) })
}) })
if(res.status === 200){
const url = location.href.split('/')
url.splice(url.length -2, 2);
location.assign(url.join('/'))
}
else{
const item = await res.json();
alert(item.message)
}
} }
async function doEdit() { async function doEdit() {
const res = await fetch('/api/edit', { const res = await fetch('/api/edit', {
@ -124,7 +133,15 @@
} }
}) })
}) })
const item = await res.json(); if(res.status === 200){
const url = location.href.split('/');
url.splice(url.length -2, 1);
location.assign(url.join('/'))
}
else{
const item = await res.json()
alert(item.message)
}
} }
</script> </script>

View File

@ -0,0 +1,249 @@
<form action="/api/img/add" method="post" enctype="multipart/form-data">
<input type="file" name="image" placeholder="avatar" />
<input type="text" name="filename" placeholder=filename />
<button type="submit">upload</button>
</form>
<script lang="ts">
import Check from '$lib/assets/icons/Check.svelte';
import SeasonSelect from '$lib/components/SeasonSelect.svelte';
import '$lib/css/action_button.css'
export let data: PageData;
let preamble = ""
let addendum = ""
import { season } from '$lib/js/season_store';
season.update(() => [])
let season_local
season.subscribe((s) => {
season_local = s
});
export let card_data ={
icon: "",
category: "",
name: "",
description: "",
tags: [],
}
export let add_info ={
preparation: "",
fermentation: {
bulk: "",
final: "",
},
baking: {
length: "",
temperature: "",
mode: "",
},
total_time: "",
cooking: "",
}
let images = []
export let portions = ""
let short_name = ""
let password
let datecreated = new Date()
let datemodified = datecreated
import type { PageData } from './$types';
import CardAdd from '$lib/components/CardAdd.svelte';
import CreateIngredientList from '$lib/components/CreateIngredientList.svelte';
export let ingredients = []
import CreateStepList from '$lib/components/CreateStepList.svelte';
export let instructions = []
function get_season(){
let season = []
const el = document.getElementById("labels");
for(var i = 0; i < el.children.length; i++){
if(el.children[i].children[0].children[0].checked){
season.push(i+1)
}
}
return season
}
function write_season(season){
const el = document.getElementById("labels");
for(var i = 0; i < season.length; i++){
el.children[i].children[0].children[0].checked = true
}
}
async function doPost () {
console.log(add_info.total_time)
const res = await fetch('/api/add', {
method: 'POST',
body: JSON.stringify({
recipe: {
...card_data,
...add_info,
images: {mediapath: short_name + '.webp', alt: "", caption: ""}, // TODO
season: season_local,
short_name,
portions,
datecreated,
datemodified,
instructions,
ingredients,
preamble,
addendum,
},
headers: {
'content-type': 'application/json',
bearer: password,
}
})
})
}
</script>
<style>
input{
display: block;
border: unset;
margin: 1rem auto;
padding: 0.5em 1em;
border-radius: 1000px;
background-color: var(--nord4);
font-size: 1.1rem;
transition: 100ms;
}
input:hover,
input:focus-visible
{
scale: 1.05 1.05;
}
.list_wrapper{
margin-inline: auto;
display: flex;
flex-direction: row;
max-width: 1000px;
gap: 2rem;
justify-content: center;
}
@media screen and (max-width: 700px){
.list_wrapper{
flex-direction: column;
}
}
input[type=password]{
box-sizing: border-box;
font-size: 1.5rem;
padding-block: 0.5em;
display: inline;
width: 100%;
}
.submit_wrapper{
position: relative;
margin-inline: auto;
width: max(300px, 50vw)
}
.submit_wrapper button{
position: absolute;
right:-1em;
bottom: -0.5em;
}
.submit_wrapper h2{
margin-bottom: 0;
}
h1{
text-align: center;
margin-bottom: 2rem;
}
.title_container{
max-width: 1000px;
display: flex;
flex-direction: column;
margin-inline: auto;
}
.title{
position: relative;
width: min(800px, 80vw);
margin-block: 2rem;
margin-inline: auto;
background-color: var(--nord6);
padding: 1rem 2rem;
}
.title p{
border: 2px solid var(--nord1);
border-radius: 10000px;
padding: 0.5em 1em;
font-size: 1.1rem;
transition: 200ms;
}
.title p:hover,
.title p:focus-within{
scale: 1.02 1.02;
}
.addendum{
font-size: 1.1rem;
max-width: 90%;
margin-inline: auto;
border: 2px solid var(--nord1);
border-radius: 45px;
padding: 1em 1em;
transition: 100ms;
}
.addendum:hover,
.addendum:focus-within
{
scale: 1.02 1.02;
}
.addendum_wrapper{
max-width: 1000px;
margin-inline: auto;
}
h3{
text-align: center;
}
</style>
<h1>Rezept erstellen</h1>
<form action="/api/img/add" method="post" enctype="multipart/form-data">
<CardAdd {card_data}></CardAdd>
<h3>Kurzname (für URL):</h3>
<input bind:value={short_name} placeholder="Kurzname"/>
<div class=title_container>
<div class=title>
<h4>Eine etwas längere Beschreibung:</h4>
<p bind:innerText={preamble} contenteditable></p>
<div class=tags>
<h4>Saison:</h4>
<SeasonSelect></SeasonSelect>
</div>
</div>
</div>
<div class=list_wrapper>
<div>
<CreateIngredientList {ingredients} {portions}></CreateIngredientList>
</div>
<div>
<CreateStepList {instructions} {add_info}></CreateStepList>
</div>
</div>
<div class=addendum_wrapper>
<h3>Nachtrag:</h3>
<div class=addendum bind:innerText={addendum} contenteditable></div>
</div>
<div class=submit_wrapper>
<h2>Neues Rezept hinzufügen:</h2>
<input type="password" placeholder=Passwort bind:value={password}>
<button type=submit class=action_button on:click={doPost}><Check fill=white width=2rem height=2rem></Check></button>
</form>
</div>