Compare commits
1 Commits
better-aut
...
feature-co
Author | SHA1 | Date | |
---|---|---|---|
cd02307a9d
|
@@ -1,88 +0,0 @@
|
|||||||
# Development Authentication Bypass
|
|
||||||
|
|
||||||
This document explains how to safely disable authentication during development.
|
|
||||||
|
|
||||||
## 🔐 Security Overview
|
|
||||||
|
|
||||||
The authentication bypass is designed with multiple layers of security:
|
|
||||||
|
|
||||||
1. **Development Mode Only**: Only works when `vite dev` is running
|
|
||||||
2. **Explicit Opt-in**: Requires setting `DEV_DISABLE_AUTH=true`
|
|
||||||
3. **Production Protection**: Build fails if enabled in production mode
|
|
||||||
4. **Environment Isolation**: Uses local environment files (gitignored)
|
|
||||||
|
|
||||||
## 🚀 Usage
|
|
||||||
|
|
||||||
### 1. Create Local Environment File
|
|
||||||
|
|
||||||
Create `.env.local` (this file is gitignored):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy from example
|
|
||||||
cp .env.local.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Enable Development Bypass
|
|
||||||
|
|
||||||
Edit `.env.local` and set:
|
|
||||||
|
|
||||||
```env
|
|
||||||
DEV_DISABLE_AUTH=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Start Development Server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll see a warning in the console:
|
|
||||||
```
|
|
||||||
🚨 AUTH DISABLED: Development mode with DEV_DISABLE_AUTH=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Access Protected Routes
|
|
||||||
|
|
||||||
Protected routes (`/rezepte/edit/*`, `/rezepte/add`) will now be accessible without authentication.
|
|
||||||
|
|
||||||
## 🛡️ Security Guarantees
|
|
||||||
|
|
||||||
### Production Safety
|
|
||||||
- **Build-time Check**: Production builds fail if `DEV_DISABLE_AUTH=true`
|
|
||||||
- **Runtime Check**: Double verification using `dev` flag from `$app/environment`
|
|
||||||
- **No Environment Leakage**: Uses `process.env` (server-only) not client environment
|
|
||||||
|
|
||||||
### Development Isolation
|
|
||||||
- **Gitignored Files**: `.env.local` is never committed
|
|
||||||
- **Example Template**: `.env.local.example` shows safe defaults
|
|
||||||
- **Clear Warnings**: Console warns when auth is disabled
|
|
||||||
|
|
||||||
## 🧪 Testing the Security
|
|
||||||
|
|
||||||
### Test Production Build Safety
|
|
||||||
```bash
|
|
||||||
# This should FAIL with security error
|
|
||||||
DEV_DISABLE_AUTH=true pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Normal Production Build
|
|
||||||
```bash
|
|
||||||
# This should succeed
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 Re-enabling Authentication
|
|
||||||
|
|
||||||
Set in `.env.local`:
|
|
||||||
```env
|
|
||||||
DEV_DISABLE_AUTH=false
|
|
||||||
```
|
|
||||||
|
|
||||||
Or simply delete/rename the `.env.local` file.
|
|
||||||
|
|
||||||
## ⚠️ Important Notes
|
|
||||||
|
|
||||||
- **Never** commit `.env.local` to git
|
|
||||||
- **Never** set `DEV_DISABLE_AUTH=true` in production environment
|
|
||||||
- The bypass provides a mock session with `rezepte_users` group access
|
|
||||||
- All other authentication flows (signin pages, etc.) remain unchanged
|
|
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -10,24 +10,22 @@
|
|||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.0.0",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@auth/core": "^0.40.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/adapter-auto": "^6.1.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/kit": "^2.37.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.1.3",
|
"svelte": "^4.0.0",
|
||||||
"@types/node": "^22.12.0",
|
"svelte-check": "^3.4.6",
|
||||||
"svelte": "^5.38.6",
|
"svelte-preprocess-import-assets": "^1.0.1",
|
||||||
"svelte-check": "^4.0.0",
|
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"vite": "^7.1.3"
|
"vite": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/sveltekit": "^1.10.0",
|
"@auth/sveltekit": "^0.14.0",
|
||||||
"@sveltejs/adapter-node": "^5.0.0",
|
"@sveltejs/adapter-node": "^2.0.0",
|
||||||
"cheerio": "1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"mongoose": "^8.0.0",
|
"mongoose": "^7.4.0",
|
||||||
"sharp": "^0.33.0"
|
"sharp": "^0.32.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3221
pnpm-lock.yaml
generated
3221
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
18
src/app.d.ts
vendored
18
src/app.d.ts
vendored
@@ -1,28 +1,12 @@
|
|||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
import type { Session } from "@auth/sveltekit";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
interface Locals {
|
// interface Locals {}
|
||||||
auth(): Promise<Session | null>;
|
|
||||||
}
|
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "@auth/sveltekit" {
|
|
||||||
interface Session {
|
|
||||||
user?: {
|
|
||||||
name?: string | null;
|
|
||||||
email?: string | null;
|
|
||||||
image?: string | null;
|
|
||||||
nickname?: string;
|
|
||||||
groups?: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
@@ -20,8 +20,8 @@ export const { handle, signIn, signOut } = SvelteKitAuth({
|
|||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
session: async ({session, token}) => {
|
session: async ({session, token}) => {
|
||||||
session.user.nickname = token.nickname as string;
|
session.user.nickname = token.nickname;
|
||||||
session.user.groups = token.groups as string[];
|
session.user.groups = token.groups;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -10,11 +10,9 @@ import * as auth from "./auth"
|
|||||||
async function authorization({ event, resolve }) {
|
async function authorization({ event, resolve }) {
|
||||||
// Protect any routes under /authenticated
|
// Protect any routes under /authenticated
|
||||||
if (event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')) {
|
if (event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')) {
|
||||||
const session = await event.locals.auth();
|
const session = await event.locals.getSession();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
// Preserve the original URL the user was trying to access
|
redirect(303, '/auth/signin');
|
||||||
const callbackUrl = encodeURIComponent(event.url.pathname + event.url.search);
|
|
||||||
redirect(303, `/login?callbackUrl=${callbackUrl}`);
|
|
||||||
}
|
}
|
||||||
else if (! session.user.groups.includes('rezepte_users')) {
|
else if (! session.user.groups.includes('rezepte_users')) {
|
||||||
// strip last dir from url
|
// strip last dir from url
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
{"terminal": "nvimterm"}
|
|
@@ -52,24 +52,6 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
|||||||
}
|
}
|
||||||
.icon{
|
.icon{
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", emoji, sans-serif;
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: -25px;
|
|
||||||
right: -25px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--nord0);
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 1.5em;
|
|
||||||
box-shadow: 0em 0em 1em 0.1em rgba(0, 0, 0, 0.6);
|
|
||||||
transition: 100ms;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
#image{
|
#image{
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@@ -153,7 +135,6 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
|||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
transition: 100ms;
|
transition: 100ms;
|
||||||
box-shadow: 0em 0em 0.2em 0.05em rgba(0, 0, 0, 0.3);
|
box-shadow: 0em 0em 0.2em 0.05em rgba(0, 0, 0, 0.3);
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
.tag:hover,
|
.tag:hover,
|
||||||
.tag:focus-visible
|
.tag:focus-visible
|
||||||
@@ -178,8 +159,7 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
|||||||
padding-inline: 1em;
|
padding-inline: 1em;
|
||||||
border-radius: 1000px;
|
border-radius: 1000px;
|
||||||
transition: 100ms;
|
transition: 100ms;
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
.card_title .category:hover,
|
.card_title .category:hover,
|
||||||
.card_title .category:focus-within
|
.card_title .category:focus-within
|
||||||
@@ -192,18 +172,6 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
|||||||
scale: 0.9 0.9;
|
scale: 0.9 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:hover,
|
|
||||||
.icon:focus-visible
|
|
||||||
{
|
|
||||||
transform: scale(1.1, 1.1);
|
|
||||||
background-color: var(--nord3);
|
|
||||||
box-shadow: 0.2em 0.2em 1em 0.1em rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon:focus {
|
|
||||||
transform: scale(0.9, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover .icon,
|
.card:hover .icon,
|
||||||
.card:focus-visible .icon
|
.card:focus-visible .icon
|
||||||
{
|
{
|
||||||
@@ -225,17 +193,17 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if icon_override || recipe.season.includes(current_month)}
|
{#if icon_override || recipe.season.includes(current_month)}
|
||||||
<button class=icon on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/icon/${recipe.icon}`}}>{recipe.icon}</button>
|
<a class=icon href="/rezepte/icon/{recipe.icon}">{recipe.icon}</a>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="card_title">
|
<div class="card_title">
|
||||||
<button class=category on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/category/${recipe.category}`}}>{recipe.category}</button>
|
<a class=category href="/rezepte/category/{recipe.category}" >{recipe.category}</a>
|
||||||
<div>
|
<div>
|
||||||
<div class=name>{@html recipe.name}</div>
|
<div class=name>{@html recipe.name}</div>
|
||||||
<div class=description>{@html recipe.description}</div>
|
<div class=description>{@html recipe.description}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=tags>
|
<div class=tags>
|
||||||
{#each recipe.tags as tag}
|
{#each recipe.tags as tag}
|
||||||
<button class=tag on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/tag/${tag}`}}>{tag}</button>
|
<a class=tag href="/rezepte/tag/{tag}">{tag}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
<script>
|
|
||||||
// get ingredients_store from IngredientsPage.svelte
|
|
||||||
import ingredients_store from './IngredientsPage.svelte';
|
|
||||||
let ingredients = [];
|
|
||||||
ingredients_store.subscribe(value => {
|
|
||||||
ingredients = value;
|
|
||||||
});
|
|
||||||
function toggleHefe(){
|
|
||||||
if(data.ingredients[i].list[j].name == "Frischhefe"){
|
|
||||||
data.ingredients[i].list[j].name = "Trockenhefe"
|
|
||||||
data.ingredients[i].list[j].amount = item.amount / 3
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
item.name = "Frischhefe"
|
|
||||||
item.amount = item.amount * 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
button{
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
svg{
|
|
||||||
width: 1.1em;
|
|
||||||
height: 1.1em;
|
|
||||||
fill: var(--blue);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<button onclick={toggleHefe}>
|
|
||||||
{item.amount} {item.unit} {item.name}
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160 352 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l111.5 0c0 0 0 0 0 0l.4 0c17.7 0 32-14.3 32-32l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1L16 432c0 17.7 14.3 32 32 32s32-14.3 32-32l0-35.1 17.6 17.5c0 0 0 0 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.8c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352l34.4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L48.4 288c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>
|
|
||||||
</button>
|
|
@@ -139,10 +139,10 @@ h2 + p{
|
|||||||
<p>({user.nickname})</p>
|
<p>({user.nickname})</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
|
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
|
||||||
<li><a href="/logout" >Log Out</a></li>
|
<li><a href="/auth/signout" >Log Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<a class=entry href=/login>Log In</a>
|
<a class=entry href=/auth/signin>Log In</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
@@ -1,44 +1,37 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { MONGO_URL } from '$env/static/private';
|
||||||
|
/*
|
||||||
|
0 - disconnected
|
||||||
|
1 - connected
|
||||||
|
2 - connecting
|
||||||
|
3 - disconnecting
|
||||||
|
4 - uninitialized
|
||||||
|
*/
|
||||||
|
const mongoConnection = {
|
||||||
|
isConnected: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/recipes';
|
export const dbConnect = async () => {
|
||||||
|
if (mongoConnection.isConnected === 1) {
|
||||||
if (!MONGODB_URI) {
|
return;
|
||||||
throw new Error('Please define the MONGODB_URI environment variable inside .env.local');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global is used here to maintain a cached connection across hot reloads
|
|
||||||
* in development. This prevents connections growing exponentially
|
|
||||||
* during API Route usage.
|
|
||||||
*/
|
|
||||||
let cached = (global as any).mongoose;
|
|
||||||
|
|
||||||
if (!cached) {
|
|
||||||
cached = (global as any).mongoose = { conn: null, promise: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function dbConnect() {
|
|
||||||
if (cached.conn) {
|
|
||||||
return cached.conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cached.promise) {
|
if (mongoose.connections.length > 0) {
|
||||||
const opts = {
|
mongoConnection.isConnected = mongoose.connections[0].readyState;
|
||||||
bufferCommands: false,
|
if (mongoConnection.isConnected === 1) {
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
|
await mongoose.disconnect();
|
||||||
return mongoose;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
cached.conn = await cached.promise;
|
await mongoose.connect(MONGO_URL ?? '');
|
||||||
return cached.conn;
|
mongoConnection.isConnected = 1;
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function dbDisconnect() {
|
export const dbDisconnect = async () => {
|
||||||
if (cached.conn) {
|
if (process.env.NODE_ENV === 'development') return;
|
||||||
await cached.conn.disconnect();
|
if (mongoConnection.isConnected === 0) return;
|
||||||
cached.conn = null;
|
|
||||||
cached.promise = null;
|
await mongoose.disconnect();
|
||||||
}
|
mongoConnection.isConnected = 0;
|
||||||
}
|
};
|
||||||
|
@@ -1,14 +1,25 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
const paymentSchema = new mongoose.Schema({
|
const PaymentSchema = new mongoose.Schema(
|
||||||
paid_by: { type: String, required: true },
|
{
|
||||||
total_amount: { type: Number, required: true },
|
type: {type: String, required: true, enum: ['payment', 'reimbursement']},
|
||||||
for_self: { type: Number, default: 0 },
|
name: {type: String, required: true},
|
||||||
for_other: { type: Number, default: 0 },
|
category : {type: String, required: false,},
|
||||||
currency: { type: String, default: 'CHF' },
|
date: {type: Date, default: Date.now},
|
||||||
description: String,
|
images: [ {
|
||||||
date: { type: Date, default: Date.now },
|
mediapath: {type: String, required: false},
|
||||||
receipt_image: String
|
}],
|
||||||
});
|
description: {type: String, required: false},
|
||||||
|
note: {type: String, required: false},
|
||||||
|
tags : [String],
|
||||||
|
original_amount: {type: Number, required: true},
|
||||||
|
total_amount: {type: Number, required: true},
|
||||||
|
personal_amounts: [{
|
||||||
|
user: {type:String, required: true},
|
||||||
|
amount: {type: Number, required: true, default:0}
|
||||||
|
}],
|
||||||
|
currency: {type: String, required: true, default: 'CHF'},
|
||||||
|
}, {timestamps: true}
|
||||||
|
);
|
||||||
|
|
||||||
export const Payment = mongoose.models.Payment || mongoose.model('Payment', paymentSchema);
|
export const Payment= mongoose.model("Payment", PaymentSchema);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { LayoutServerLoad } from "./$types"
|
import type { PageServerLoad } from "./$types"
|
||||||
|
|
||||||
export const load : LayoutServerLoad = (async ({locals}) => {
|
export const load : PageServerLoad = (async ({locals}) => {
|
||||||
return {
|
return {
|
||||||
session: await locals.auth(),
|
session: await locals.auth(),
|
||||||
}
|
}
|
||||||
|
@@ -4,68 +4,50 @@ import path from 'path';
|
|||||||
import { mkdir } from 'fs/promises';
|
import { mkdir } from 'fs/promises';
|
||||||
import { Payment } from '$lib/models/Payment'; // adjust path as needed
|
import { Payment } from '$lib/models/Payment'; // adjust path as needed
|
||||||
import { dbConnect, dbDisconnect } from '$lib/db/db';
|
import { dbConnect, dbDisconnect } from '$lib/db/db';
|
||||||
import { error } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
const UPLOAD_DIR = './static/cospend';
|
const UPLOAD_DIR = '/var/lib/www/static/test';
|
||||||
const BASE_CURRENCY = 'CHF'; // Default currency
|
const BASE_CURRENCY = 'CHF'; // Default currency
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||||
let auth = await locals.auth();
|
|
||||||
if(!auth){
|
|
||||||
throw error(401, "Not logged in")
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const name = formData.get('name') as string;
|
const name = formData.get('name') as string;
|
||||||
const category = formData.get('category') as string;
|
const category = formData.get('category') as string;
|
||||||
const transaction_date= new Date(formData.get('transaction_date') as string);
|
const date= new Date(formData.get('date') as string);
|
||||||
const description = formData.get('description') as string;
|
const description = formData.get('description') as string;
|
||||||
const note = formData.get('note') as string;
|
const note = formData.get('note') as string;
|
||||||
const tags = JSON.parse(formData.get('tags') as string) as string[];
|
const tags = JSON.parse(formData.get('tags') as string) as string[];
|
||||||
const paid_by = formData.get('paid_by') as string
|
|
||||||
const type = formData.get('type') as string
|
|
||||||
|
|
||||||
let currency = formData.get('currency') as string;
|
let currency = formData.get('currency') as string;
|
||||||
let original_amount = parseFloat(formData.get('original_amount') as string);
|
let original_amount = parseFloat(formData.get('original_amount') as string);
|
||||||
let total_amount = NaN;
|
let total_amount = NaN;
|
||||||
|
|
||||||
let for_self = parseFloat(formData.get('for_self') as string);
|
|
||||||
let for_other = parseFloat(formData.get('for_other') as string);
|
|
||||||
let conversion_rate = 1.0; // Default conversion rate
|
|
||||||
|
|
||||||
// if currency is not BASE_CURRENCY, fetch current conversion rate using frankfurter API and date in YYYY-MM-DD format
|
// if currency is not BASE_CURRENCY, fetch current conversion rate using frankfurter API and date in YYYY-MM-DD format
|
||||||
if (!currency || currency === BASE_CURRENCY) {
|
if (!currency || currency === BASE_CURRENCY) {
|
||||||
currency = BASE_CURRENCY;
|
currency = BASE_CURRENCY;
|
||||||
total_amount = original_amount;
|
total_amount = parseFloat(formData.get('total_amount') as string);
|
||||||
} else {
|
} else {
|
||||||
console.log(transaction_date);
|
const date_fmt = date.toISOString().split('T')[0]; // Convert date to YYYY-MM-DD format
|
||||||
const date_fmt = transaction_date.toISOString().split('T')[0]; // Convert date to YYYY-MM-DD format
|
|
||||||
// Fetch conversion rate logic here (not implemented in this example)
|
// Fetch conversion rate logic here (not implemented in this example)
|
||||||
console.log(`Fetching conversion rate for ${currency} to ${BASE_CURRENCY} on ${date_fmt}`);
|
|
||||||
const res = await fetch(`https://api.frankfurter.app/${date_fmt}?from=${currency}&to=${BASE_CURRENCY}`)
|
const res = await fetch(`https://api.frankfurter.app/${date_fmt}?from=${currency}&to=${BASE_CURRENCY}`)
|
||||||
console.log(res);
|
const { result } = await res.json();
|
||||||
const result = await res.json();
|
if (!result || !result[BASE_CURRENCY]) {
|
||||||
console.log(result);
|
|
||||||
if (!result || !result.rates[BASE_CURRENCY]) {
|
|
||||||
return new Response(JSON.stringify({ message: 'Currency conversion failed.' }), { status: 400 });
|
return new Response(JSON.stringify({ message: 'Currency conversion failed.' }), { status: 400 });
|
||||||
}
|
}
|
||||||
// Assuming you want to convert the total amount to BASE_CURRENCY
|
// Assuming you want to convert the total amount to BASE_CURRENCY
|
||||||
conversion_rate = parseFloat(result.rates[BASE_CURRENCY]);
|
const conversionRate = parseFloat(result.rates[BASE_CURRENCY]);
|
||||||
console.log(`Conversion rate from ${currency} to ${BASE_CURRENCY} on ${date_fmt}: ${conversion_rate}`);
|
alert(`Conversion rate from ${currency} to ${BASE_CURRENCY} on ${date_fmt}: ${conversionRate}`);
|
||||||
total_amount = original_amount * conversion_rate;
|
total_amount = original_amount * conversionRate;
|
||||||
for_self = for_self * conversion_rate;
|
|
||||||
for_other = for_other * conversion_rate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//const personal_amounts = JSON.parse(formData.get('personal_amounts') as string) as { user: string, amount: number }[];
|
const personal_amounts = JSON.parse(formData.get('personal_amounts') as string) as { user: string, amount: number }[];
|
||||||
|
|
||||||
|
|
||||||
if (!name || isNaN(total_amount)) {
|
if (!name || isNaN(total_amount)) {
|
||||||
return new Response(JSON.stringify({ message: 'Invalid required fields.' }), { status: 400 });
|
return new Response(JSON.stringify({ message: 'Invalid required fields.' }), { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(UPLOAD_DIR, { recursive: true });
|
// await mkdir(UPLOAD_DIR, { recursive: true });
|
||||||
|
|
||||||
const images: { mediapath: string }[] = [];
|
const images: { mediapath: string }[] = [];
|
||||||
const imageFiles = formData.getAll('images') as File[];
|
const imageFiles = formData.getAll('images') as File[];
|
||||||
@@ -75,40 +57,35 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
const buffer = Buffer.from(arrayBuffer);
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
const safeName = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
|
const safeName = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
|
||||||
const fullPath = path.join(UPLOAD_DIR, safeName);
|
const fullPath = path.join(UPLOAD_DIR, safeName);
|
||||||
fs.writeFileSync(fullPath, buffer);
|
//fs.writeFileSync(fullPath, buffer);
|
||||||
images.push({ mediapath: `/static/test/${safeName}` });
|
images.push({ mediapath: `/static/test/${safeName}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbConnect();
|
await dbConnect();
|
||||||
const payment = new Payment({
|
const payment = new Payment({
|
||||||
type,
|
|
||||||
name,
|
name,
|
||||||
category,
|
category,
|
||||||
transaction_date,
|
date,
|
||||||
images,
|
|
||||||
description,
|
description,
|
||||||
note,
|
note,
|
||||||
tags,
|
tags,
|
||||||
original_amount,
|
|
||||||
total_amount,
|
total_amount,
|
||||||
paid_by,
|
original_amount,
|
||||||
for_self,
|
|
||||||
for_other,
|
|
||||||
conversion_rate,
|
|
||||||
currency,
|
currency,
|
||||||
|
personal_amounts,
|
||||||
|
images
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// let auth = await locals.auth();
|
||||||
|
// // if(!auth){
|
||||||
|
// throw error(401, "Not logged in")
|
||||||
|
// }
|
||||||
|
|
||||||
try{
|
try{
|
||||||
await Payment.create(payment);
|
await Payment.create(payment);
|
||||||
} catch(e){
|
} catch(e){
|
||||||
|
throw error(400, e)
|
||||||
return new Response(JSON.stringify({ message: `Error creating payment event. ${e}` }), { status: 500 });
|
|
||||||
}
|
}
|
||||||
await dbDisconnect();
|
await dbDisconnect();
|
||||||
return new Response(JSON.stringify({ message: 'Payment event created successfully.' }), { status: 201 });
|
return new Response(JSON.stringify({ message: 'Payment event created successfully.' }), { status: 201 });
|
@@ -1,38 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { json } from '@sveltejs/kit';
|
|
||||||
import { Payment } from '$lib/models/Payment'; // adjust path as needed
|
|
||||||
import { dbConnect, dbDisconnect } from '$lib/db/db';
|
|
||||||
|
|
||||||
const UPLOAD_DIR = '/var/lib/www/static/test';
|
|
||||||
const BASE_CURRENCY = 'CHF'; // Default currency
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ request, locals }) => {
|
|
||||||
await dbConnect();
|
|
||||||
|
|
||||||
const result = await Payment.aggregate([
|
|
||||||
{
|
|
||||||
$group: {
|
|
||||||
_id: "$paid_by",
|
|
||||||
totalPaid: { $sum: "$total_amount" },
|
|
||||||
totalForSelf: { $sum: { $ifNull: ["$for_self", 0] } },
|
|
||||||
totalForOther: { $sum: { $ifNull: ["$for_other", 0] } }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
_id: 0,
|
|
||||||
paid_by: "$_id",
|
|
||||||
netTotal: {
|
|
||||||
$multiply: [
|
|
||||||
{ $add: [
|
|
||||||
{ $subtract: ["$totalPaid", "$totalForSelf"] },
|
|
||||||
"$totalForOther"
|
|
||||||
] },
|
|
||||||
0.5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
await dbDisconnect();
|
|
||||||
return json(result);
|
|
||||||
};
|
|
@@ -1,20 +0,0 @@
|
|||||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { Payment } from '$lib/models/Payment';
|
|
||||||
import { dbConnect, dbDisconnect } from '$lib/db/db';
|
|
||||||
import { error } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({params}) => {
|
|
||||||
await dbConnect();
|
|
||||||
const number_payments = 10;
|
|
||||||
const number_skip = params.pageno ? (parseInt(params.pageno) - 1 ) * number_payments : 0;
|
|
||||||
let payments = await Payment.find()
|
|
||||||
.sort({ transaction_date: -1 })
|
|
||||||
.skip(number_skip)
|
|
||||||
.limit(number_payments);
|
|
||||||
await dbDisconnect();
|
|
||||||
|
|
||||||
if(payments == null){
|
|
||||||
throw error(404, "No more payments found");
|
|
||||||
}
|
|
||||||
return json(payments);
|
|
||||||
};
|
|
7
src/routes/cospend/+layout.server.ts
Normal file
7
src/routes/cospend/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { PageServerLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load : PageServerLoad = (async ({locals}) => {
|
||||||
|
return {
|
||||||
|
session: await locals.auth(),
|
||||||
|
}
|
||||||
|
});
|
18
src/routes/cospend/+layout.svelte
Normal file
18
src/routes/cospend/+layout.svelte
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
import Header from '$lib/components/Header.svelte'
|
||||||
|
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||||
|
export let data
|
||||||
|
let username = ""
|
||||||
|
if(data.user){
|
||||||
|
username = data.user.username
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<Header>
|
||||||
|
<ul class=site_header slot=links>
|
||||||
|
<li><a href="/glaube/gebete">Gebete</a></li>
|
||||||
|
<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
|
||||||
|
<li><a href="/glaube/predigten">Predigten</a></li>
|
||||||
|
</ul>
|
||||||
|
<UserHeader {username} slot=right_side></UserHeader>
|
||||||
|
<slot></slot>
|
||||||
|
</Header>
|
133
src/routes/cospend/+page.svelte
Normal file
133
src/routes/cospend/+page.svelte
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<style>
|
||||||
|
h1{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h1>Settlement Plan</h1>
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let name = '';
|
||||||
|
let category = '';
|
||||||
|
let date = new Date().toISOString().split('T')[0]; // format as yyyy-mm-dd
|
||||||
|
let images: File[] = [];
|
||||||
|
let description = '';
|
||||||
|
let note = '';
|
||||||
|
let tags = '';
|
||||||
|
let original_amount = 0;
|
||||||
|
let currency = 'CHF';
|
||||||
|
let payment_method = '';
|
||||||
|
|
||||||
|
let personal_amounts = [
|
||||||
|
{ user: 'alexander', amount: 0 },
|
||||||
|
{ user: 'anna', amount: 0 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', name);
|
||||||
|
formData.append('category', category);
|
||||||
|
formData.append('dateCreated', date);
|
||||||
|
formData.append('description', description);
|
||||||
|
formData.append('note', note);
|
||||||
|
formData.append('tags', JSON.stringify(tags.split(',').map(tag => tag.trim())));
|
||||||
|
formData.append('total_amount', total_amount.toString());
|
||||||
|
formData.append('currency', currency);
|
||||||
|
formData.append('payment_method', payment_method);
|
||||||
|
formData.append('personal_amounts', JSON.stringify(personal_amounts));
|
||||||
|
|
||||||
|
images.forEach((file, index) => {
|
||||||
|
formData.append('images', file);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch('/api/cospend/add', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
alert(result.message);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={handleSubmit} class="flex flex-col gap-4 max-w-xl">
|
||||||
|
<label>
|
||||||
|
Name:
|
||||||
|
<input type="text" bind:value={name} required />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Category:
|
||||||
|
<input type="text" bind:value={category} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Date Created:
|
||||||
|
<input type="date" bind:value={date} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Images:
|
||||||
|
<input type="file" multiple accept="image/*" on:change={(e) => images = Array.from(e.target.files)} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Description:
|
||||||
|
<textarea bind:value={description}></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Note:
|
||||||
|
<textarea bind:value={note}></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Tags (comma separated):
|
||||||
|
<input type="text" bind:value={tags} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Total Amount:
|
||||||
|
<input type="number" bind:value={original_amount} step="0.01" required />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Personal Amounts</legend>
|
||||||
|
{#each personal_amounts as entry, i}
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<label>{entry.user}</label>
|
||||||
|
<input type="number" bind:value={personal_amounts[i].amount} step="0.01" required />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Currency:
|
||||||
|
<select bind:value={currency}>
|
||||||
|
<option value="CHF">CHF</option>
|
||||||
|
<option value="EUR">EUR</option>
|
||||||
|
<option value="USD">USD</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Payment Method:
|
||||||
|
<select bind:value={payment_method}>
|
||||||
|
<option value="">-- Select --</option>
|
||||||
|
<option value="cash">Cash</option>
|
||||||
|
<option value="bank_transfer">Bank Transfer</option>
|
||||||
|
<option value="credit_card">Credit Card</option>
|
||||||
|
<option value="twint">Twint</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit">Save Payment</button>
|
||||||
|
</form>
|
@@ -1,18 +0,0 @@
|
|||||||
import { error } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export async function load({ fetch, params}) {
|
|
||||||
let balance_res = await fetch(`/api/cospend/balance`);
|
|
||||||
if (!balance_res.ok) {
|
|
||||||
throw error(balance_res.status, `Failed to fetch balance`);
|
|
||||||
}
|
|
||||||
let balance = await balance_res.json();
|
|
||||||
const items_res = await fetch(`/api/cospend/page/1`);
|
|
||||||
if (!items_res.ok) {
|
|
||||||
throw error(items_res.status, `Failed to fetch items`);
|
|
||||||
}
|
|
||||||
let items = await items_res.json();
|
|
||||||
return {
|
|
||||||
balance,
|
|
||||||
items
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
import type { LayoutServerLoad } from "./$types"
|
import type { PageServerLoad } from "./$types"
|
||||||
|
|
||||||
export const load : LayoutServerLoad = (async ({locals}) => {
|
export const load : PageServerLoad = (async ({locals}) => {
|
||||||
return {
|
return {
|
||||||
session: await locals.auth(),
|
session: await locals.auth(),
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,10 @@
|
|||||||
import Header from '$lib/components/Header.svelte'
|
import Header from '$lib/components/Header.svelte'
|
||||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||||
export let data
|
export let data
|
||||||
|
let username = ""
|
||||||
|
if(data.user){
|
||||||
|
username = data.user.username
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<Header>
|
<Header>
|
||||||
<ul class=site_header slot=links>
|
<ul class=site_header slot=links>
|
||||||
@@ -9,6 +13,6 @@ export let data
|
|||||||
<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
|
<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
|
||||||
<li><a href="/glaube/predigten">Predigten</a></li>
|
<li><a href="/glaube/predigten">Predigten</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<UserHeader user={data.session?.user} slot=right_side></UserHeader>
|
<UserHeader {username} slot=right_side></UserHeader>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</Header>
|
</Header>
|
||||||
|
@@ -1,44 +0,0 @@
|
|||||||
import type { RequestHandler } from './$types';
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ url }) => {
|
|
||||||
const callbackUrl = url.searchParams.get('callbackUrl') || '/';
|
|
||||||
|
|
||||||
// Create a minimal page with site styling that immediately triggers auth
|
|
||||||
const html = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Login</title>
|
|
||||||
<style>
|
|
||||||
:root{
|
|
||||||
--nord0: #2E3440;
|
|
||||||
--nord1: #3B4252;
|
|
||||||
--nord4: #D8DEE9;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: var(--nord1);
|
|
||||||
color: var(--nord4);
|
|
||||||
font-family: sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<form id="signin-form" method="POST" action="/auth/signin/authentik">
|
|
||||||
<input type="hidden" name="callbackUrl" value="${callbackUrl}" />
|
|
||||||
</form>
|
|
||||||
<script>
|
|
||||||
// Immediately submit the form to trigger auth flow
|
|
||||||
document.getElementById('signin-form').submit();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
return new Response(html, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/html'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@@ -1,44 +0,0 @@
|
|||||||
import type { RequestHandler } from './$types';
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ url }) => {
|
|
||||||
const callbackUrl = url.searchParams.get('callbackUrl') || '/';
|
|
||||||
|
|
||||||
// Create a minimal page with site styling that immediately triggers logout
|
|
||||||
const html = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Logout</title>
|
|
||||||
<style>
|
|
||||||
:root{
|
|
||||||
--nord0: #2E3440;
|
|
||||||
--nord1: #3B4252;
|
|
||||||
--nord4: #D8DEE9;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: var(--nord1);
|
|
||||||
color: var(--nord4);
|
|
||||||
font-family: sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<form id="signout-form" method="POST" action="/auth/signout">
|
|
||||||
<input type="hidden" name="callbackUrl" value="${callbackUrl}" />
|
|
||||||
</form>
|
|
||||||
<script>
|
|
||||||
// Immediately submit the form to trigger logout flow
|
|
||||||
document.getElementById('signout-form').submit();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
return new Response(html, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/html'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@@ -1,6 +1,6 @@
|
|||||||
import type { LayoutServerLoad } from "./$types"
|
import type { PageServerLoad } from "./$types"
|
||||||
|
|
||||||
export const load : LayoutServerLoad = async ({locals}) => {
|
export const load : PageServerLoad = async ({locals}) => {
|
||||||
return {
|
return {
|
||||||
session: await locals.auth()
|
session: await locals.auth()
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
export async function load({locals}) {
|
export async function load({locals}) {
|
||||||
const session = await locals.auth();
|
|
||||||
return {
|
return {
|
||||||
user: session?.user
|
user: locals.user
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
export async function load({ fetch, params, locals}) {
|
export async function load({ fetch, params, locals}) {
|
||||||
let current_month = new Date().getMonth() + 1
|
let current_month = new Date().getMonth() + 1
|
||||||
const res = await fetch(`/api/rezepte/items/${params.name}`);
|
const res = await fetch(`/api/rezepte/items/${params.name}`);
|
||||||
const recipe = await res.json();
|
const recipe = await res.json();
|
||||||
const session = await locals.auth();
|
|
||||||
return {recipe: recipe,
|
return {recipe: recipe,
|
||||||
user: session?.user
|
user: locals.user
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
{"terminal": "nvimterm"}
|
|
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
document.addEventListener('load', function() { alert(1);document.querySelector('.detailImageContainer').addEventListener('click',function(){document.querySelector(".btnPlay[title='Play'],.btnPlay[title='Resume']").click()});});
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
Reference in New Issue
Block a user