Compare commits
	
		
			6 Commits
		
	
	
		
			feature-co
			...
			svelte5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						7f06717615
	
				 | 
					
					
						|||
| 
						
						
							
						
						4f34ff5329
	
				 | 
					
					
						|||
| 
						
						
							
						
						69e719780c
	
				 | 
					
					
						|||
| 
						
						
							
						
						e2a76d4080
	
				 | 
					
					
						|||
| 
						
						
							
						
						b847b8f1c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						b861f9aeec
	
				 | 
					
					
						
							
								
								
									
										88
									
								
								README_DEV_AUTH.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								README_DEV_AUTH.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
# 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
 | 
			
		||||
							
								
								
									
										3318
									
								
								package-lock_old.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3318
									
								
								package-lock_old.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								package.json
									
									
									
									
									
								
							@@ -10,22 +10,24 @@
 | 
			
		||||
		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
 | 
			
		||||
		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
 | 
			
		||||
	},
 | 
			
		||||
	"packageManager": "pnpm@9.0.0",
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@sveltejs/adapter-auto": "^3.0.0",
 | 
			
		||||
		"@sveltejs/kit": "^2.0.0",
 | 
			
		||||
		"@sveltejs/vite-plugin-svelte": "^3.0.0",
 | 
			
		||||
		"svelte": "^4.0.0",
 | 
			
		||||
		"svelte-check": "^3.4.6",
 | 
			
		||||
		"svelte-preprocess-import-assets": "^1.0.1",
 | 
			
		||||
		"@auth/core": "^0.40.0",
 | 
			
		||||
		"@sveltejs/adapter-auto": "^6.1.0",
 | 
			
		||||
		"@sveltejs/kit": "^2.37.0",
 | 
			
		||||
		"@sveltejs/vite-plugin-svelte": "^6.1.3",
 | 
			
		||||
		"@types/node": "^22.12.0",
 | 
			
		||||
		"svelte": "^5.38.6",
 | 
			
		||||
		"svelte-check": "^4.0.0",
 | 
			
		||||
		"tslib": "^2.6.0",
 | 
			
		||||
		"typescript": "^5.1.6",
 | 
			
		||||
		"vite": "^5.0.0"
 | 
			
		||||
		"vite": "^7.1.3"
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@auth/sveltekit": "^0.14.0",
 | 
			
		||||
		"@sveltejs/adapter-node": "^2.0.0",
 | 
			
		||||
		"@auth/sveltekit": "^1.10.0",
 | 
			
		||||
		"@sveltejs/adapter-node": "^5.0.0",
 | 
			
		||||
		"cheerio": "1.0.0-rc.12",
 | 
			
		||||
		"mongoose": "^7.4.0",
 | 
			
		||||
		"sharp": "^0.32.3"
 | 
			
		||||
		"mongoose": "^8.0.0",
 | 
			
		||||
		"sharp": "^0.33.0"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3211
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3211
									
								
								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,12 +1,28 @@
 | 
			
		||||
// See https://kit.svelte.dev/docs/types#app
 | 
			
		||||
// for information about these interfaces
 | 
			
		||||
import type { Session } from "@auth/sveltekit";
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
	namespace App {
 | 
			
		||||
		// interface Error {}
 | 
			
		||||
		// interface Locals {}
 | 
			
		||||
		interface Locals {
 | 
			
		||||
			auth(): Promise<Session | null>;
 | 
			
		||||
		}
 | 
			
		||||
		// interface PageData {}
 | 
			
		||||
		// interface Platform {}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module "@auth/sveltekit" {
 | 
			
		||||
	interface Session {
 | 
			
		||||
		user?: {
 | 
			
		||||
			name?: string | null;
 | 
			
		||||
			email?: string | null;
 | 
			
		||||
			image?: string | null;
 | 
			
		||||
			nickname?: string;
 | 
			
		||||
			groups?: string[];
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {};
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,8 @@ export const { handle, signIn, signOut } = SvelteKitAuth({
 | 
			
		||||
			return token;
 | 
			
		||||
		},
 | 
			
		||||
		session: async ({session, token}) => {
 | 
			
		||||
			session.user.nickname = token.nickname;
 | 
			
		||||
			session.user.groups = token.groups;
 | 
			
		||||
			session.user.nickname = token.nickname as string;
 | 
			
		||||
			session.user.groups = token.groups as string[];
 | 
			
		||||
			return session;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import * as auth from "./auth"
 | 
			
		||||
async function authorization({ event, resolve }) {
 | 
			
		||||
	// Protect any routes under /authenticated
 | 
			
		||||
	if (event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')) {
 | 
			
		||||
   const session = await event.locals.getSession();
 | 
			
		||||
   const session = await event.locals.auth();
 | 
			
		||||
		if (!session) {
 | 
			
		||||
			redirect(303, '/auth/signin');
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								src/lib/components/.jukit/.jukit_info.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/lib/components/.jukit/.jukit_info.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"terminal": "nvimterm"}
 | 
			
		||||
@@ -52,6 +52,24 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
 | 
			
		||||
}
 | 
			
		||||
.icon{
 | 
			
		||||
	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{
 | 
			
		||||
	width: 300px;
 | 
			
		||||
@@ -135,6 +153,7 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
 | 
			
		||||
	margin-bottom: 0.5em;
 | 
			
		||||
	transition: 100ms;
 | 
			
		||||
	box-shadow: 0em 0em 0.2em 0.05em rgba(0, 0, 0, 0.3);
 | 
			
		||||
	border: none;
 | 
			
		||||
}
 | 
			
		||||
.tag:hover,
 | 
			
		||||
.tag:focus-visible
 | 
			
		||||
@@ -159,7 +178,8 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
 | 
			
		||||
	padding-inline: 1em;
 | 
			
		||||
	border-radius: 1000px;
 | 
			
		||||
	transition: 100ms;
 | 
			
		||||
 | 
			
		||||
	border: none;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.card_title .category:hover,
 | 
			
		||||
.card_title .category:focus-within
 | 
			
		||||
@@ -172,6 +192,18 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
 | 
			
		||||
	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:focus-visible .icon
 | 
			
		||||
{
 | 
			
		||||
@@ -193,17 +225,17 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	{#if icon_override || recipe.season.includes(current_month)}
 | 
			
		||||
		<a class=icon href="/rezepte/icon/{recipe.icon}">{recipe.icon}</a>
 | 
			
		||||
		<button class=icon on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/icon/${recipe.icon}`}}>{recipe.icon}</button>
 | 
			
		||||
	{/if}
 | 
			
		||||
	<div class="card_title">
 | 
			
		||||
		<a class=category href="/rezepte/category/{recipe.category}" >{recipe.category}</a>
 | 
			
		||||
		<button class=category on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/category/${recipe.category}`}}>{recipe.category}</button>
 | 
			
		||||
		<div>
 | 
			
		||||
			<div class=name>{@html recipe.name}</div>
 | 
			
		||||
			<div class=description>{@html recipe.description}</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class=tags>
 | 
			
		||||
		{#each recipe.tags as tag}
 | 
			
		||||
			<a class=tag href="/rezepte/tag/{tag}">{tag}</a>
 | 
			
		||||
			<button class=tag on:click={(e) => {e.stopPropagation(); window.location.href = `/rezepte/tag/${tag}`}}>{tag}</button>
 | 
			
		||||
		{/each}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								src/lib/components/HefeSwapper.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/lib/components/HefeSwapper.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
<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>
 | 
			
		||||
							
								
								
									
										44
									
								
								src/lib/db/db.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/lib/db/db.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/recipes';
 | 
			
		||||
 | 
			
		||||
if (!MONGODB_URI) {
 | 
			
		||||
  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) {
 | 
			
		||||
    const opts = {
 | 
			
		||||
      bufferCommands: false,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
 | 
			
		||||
      return mongoose;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  cached.conn = await cached.promise;
 | 
			
		||||
  return cached.conn;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function dbDisconnect() {
 | 
			
		||||
  if (cached.conn) {
 | 
			
		||||
    await cached.conn.disconnect();
 | 
			
		||||
    cached.conn = null;
 | 
			
		||||
    cached.promise = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/lib/models/Payment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/models/Payment.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const paymentSchema = new mongoose.Schema({
 | 
			
		||||
  paid_by: { type: String, required: true },
 | 
			
		||||
  total_amount: { type: Number, required: true },
 | 
			
		||||
  for_self: { type: Number, default: 0 },
 | 
			
		||||
  for_other: { type: Number, default: 0 },
 | 
			
		||||
  currency: { type: String, default: 'CHF' },
 | 
			
		||||
  description: String,
 | 
			
		||||
  date: { type: Date, default: Date.now },
 | 
			
		||||
  receipt_image: String
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const Payment = mongoose.models.Payment || mongoose.model('Payment', paymentSchema);
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import type { PageServerLoad } from "./$types"
 | 
			
		||||
import type { LayoutServerLoad } from "./$types"
 | 
			
		||||
 | 
			
		||||
export const load : PageServerLoad = (async ({locals}) => {
 | 
			
		||||
export const load : LayoutServerLoad = (async ({locals}) => {
 | 
			
		||||
	return {
 | 
			
		||||
		session: await locals.auth(),
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/routes/(main)/login/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/routes/(main)/login/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { signIn } from "../../../auth"
 | 
			
		||||
import type { Actions } from "./$types"
 | 
			
		||||
export const actions: Actions = { default: signIn }
 | 
			
		||||
							
								
								
									
										3
									
								
								src/routes/(main)/logout/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/routes/(main)/logout/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import { signIn } from "../../../auth"
 | 
			
		||||
import type { Actions } from "./$types"
 | 
			
		||||
export const actions: Actions = { default: signIn }
 | 
			
		||||
							
								
								
									
										119
									
								
								src/routes/api/cospend/add/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/routes/api/cospend/add/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
import type { RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import { mkdir } from 'fs/promises';
 | 
			
		||||
import { Payment } from '$lib/models/Payment'; // adjust path as needed
 | 
			
		||||
import { dbConnect, dbDisconnect } from '$lib/db/db';
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
 | 
			
		||||
const UPLOAD_DIR = './static/cospend';
 | 
			
		||||
const BASE_CURRENCY = 'CHF'; // Default currency
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const name = formData.get('name') as string;
 | 
			
		||||
    const category = formData.get('category') as string;
 | 
			
		||||
    const transaction_date= new Date(formData.get('transaction_date') as string);
 | 
			
		||||
    const description = formData.get('description') as string;
 | 
			
		||||
    const note = formData.get('note') 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 original_amount = parseFloat(formData.get('original_amount') as string);
 | 
			
		||||
    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 || currency === BASE_CURRENCY) {
 | 
			
		||||
        currency = BASE_CURRENCY;
 | 
			
		||||
    	total_amount = original_amount;
 | 
			
		||||
    } else {
 | 
			
		||||
	console.log(transaction_date);
 | 
			
		||||
        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)
 | 
			
		||||
	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}`)
 | 
			
		||||
	console.log(res);
 | 
			
		||||
	const result = await res.json();
 | 
			
		||||
	console.log(result);
 | 
			
		||||
	if (!result || !result.rates[BASE_CURRENCY]) {
 | 
			
		||||
	    return new Response(JSON.stringify({ message: 'Currency conversion failed.' }), { status: 400 });
 | 
			
		||||
	}
 | 
			
		||||
	// Assuming you want to convert the total amount to BASE_CURRENCY
 | 
			
		||||
	conversion_rate = parseFloat(result.rates[BASE_CURRENCY]);
 | 
			
		||||
	console.log(`Conversion rate from ${currency} to ${BASE_CURRENCY} on ${date_fmt}: ${conversion_rate}`);
 | 
			
		||||
	total_amount = original_amount * conversion_rate;
 | 
			
		||||
	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 }[];
 | 
			
		||||
 | 
			
		||||
    if (!name || isNaN(total_amount)) {
 | 
			
		||||
      return new Response(JSON.stringify({ message: 'Invalid required fields.' }), { status: 400 });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await mkdir(UPLOAD_DIR, { recursive: true });
 | 
			
		||||
 | 
			
		||||
    const images: { mediapath: string }[] = [];
 | 
			
		||||
    const imageFiles = formData.getAll('images') as File[];
 | 
			
		||||
 | 
			
		||||
    for (const file of imageFiles) {
 | 
			
		||||
      const arrayBuffer = await file.arrayBuffer();
 | 
			
		||||
      const buffer = Buffer.from(arrayBuffer);
 | 
			
		||||
      const safeName = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9_.-]/g, '_')}`;
 | 
			
		||||
      const fullPath = path.join(UPLOAD_DIR, safeName);
 | 
			
		||||
      fs.writeFileSync(fullPath, buffer);
 | 
			
		||||
      images.push({ mediapath: `/static/test/${safeName}` });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await dbConnect();
 | 
			
		||||
    const payment = new Payment({
 | 
			
		||||
      type,
 | 
			
		||||
      name,
 | 
			
		||||
      category,
 | 
			
		||||
      transaction_date,
 | 
			
		||||
      images,
 | 
			
		||||
      description,
 | 
			
		||||
      note,
 | 
			
		||||
      tags,
 | 
			
		||||
      original_amount,
 | 
			
		||||
      total_amount,
 | 
			
		||||
      paid_by,
 | 
			
		||||
      for_self,
 | 
			
		||||
      for_other,
 | 
			
		||||
      conversion_rate,
 | 
			
		||||
      currency,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    try{
 | 
			
		||||
    	await Payment.create(payment);
 | 
			
		||||
    } catch(e){
 | 
			
		||||
 | 
			
		||||
	    return new Response(JSON.stringify({ message: `Error creating payment event. ${e}` }), { status: 500 });
 | 
			
		||||
    }
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    return new Response(JSON.stringify({ message: 'Payment event created successfully.' }), { status: 201 });
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.error(err);
 | 
			
		||||
    return new Response(JSON.stringify({ message: 'Error processing request.' }), { status: 500 });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										38
									
								
								src/routes/api/cospend/balance/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/routes/api/cospend/balance/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										20
									
								
								src/routes/api/cospend/page/[pageno]/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/routes/api/cospend/page/[pageno]/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										18
									
								
								src/routes/cospend/+page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/routes/cospend/+page.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
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 { PageServerLoad } from "./$types"
 | 
			
		||||
import type { LayoutServerLoad } from "./$types"
 | 
			
		||||
 | 
			
		||||
export const load : PageServerLoad = (async ({locals}) => {
 | 
			
		||||
export const load : LayoutServerLoad = (async ({locals}) => {
 | 
			
		||||
	return {
 | 
			
		||||
		session: await locals.auth(),
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,6 @@
 | 
			
		||||
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>
 | 
			
		||||
@@ -13,6 +9,6 @@ if(data.user){
 | 
			
		||||
	<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
 | 
			
		||||
	<li><a href="/glaube/predigten">Predigten</a></li>
 | 
			
		||||
	</ul>
 | 
			
		||||
	<UserHeader {username} slot=right_side></UserHeader>
 | 
			
		||||
	<UserHeader user={data.session?.user} slot=right_side></UserHeader>
 | 
			
		||||
	<slot></slot>
 | 
			
		||||
</Header>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import type { PageServerLoad } from "./$types"
 | 
			
		||||
import type { LayoutServerLoad } from "./$types"
 | 
			
		||||
 | 
			
		||||
export const load : PageServerLoad = async ({locals}) => {
 | 
			
		||||
export const load : LayoutServerLoad = async ({locals}) => {
 | 
			
		||||
	return {
 | 
			
		||||
		session: await locals.auth()
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
export async function load({locals}) {
 | 
			
		||||
    const session = await locals.auth();
 | 
			
		||||
    return {
 | 
			
		||||
	user: locals.user
 | 
			
		||||
	user: session?.user
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
import type { PageLoad } from "./$types";
 | 
			
		||||
import type { PageServerLoad } from "./$types";
 | 
			
		||||
 | 
			
		||||
export async function load({ fetch, params, locals}) {
 | 
			
		||||
    let current_month = new Date().getMonth() + 1
 | 
			
		||||
    const res = await fetch(`/api/rezepte/items/${params.name}`);
 | 
			
		||||
    const recipe = await res.json();
 | 
			
		||||
    const session = await locals.auth();
 | 
			
		||||
    return {recipe: recipe,
 | 
			
		||||
	user: locals.user
 | 
			
		||||
	user: session?.user
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								static/other/.jukit/.jukit_info.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/other/.jukit/.jukit_info.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"terminal": "nvimterm"}
 | 
			
		||||
							
								
								
									
										2
									
								
								static/other/jellyfin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								static/other/jellyfin.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
 | 
			
		||||
document.addEventListener('load', function() { alert(1);document.querySelector('.detailImageContainer').addEventListener('click',function(){document.querySelector(".btnPlay[title='Play'],.btnPlay[title='Resume']").click()});});
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/test/1749501265645_angelus0001.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/test/1749501265645_angelus0001.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.4 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/test/1749501265648_Anna_ID.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/test/1749501265648_Anna_ID.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user