Compare commits
1 Commits
master
...
feature-co
Author | SHA1 | Date | |
---|---|---|---|
cd02307a9d |
37
src/lib/db/db.ts
Normal file
37
src/lib/db/db.ts
Normal file
@ -0,0 +1,37 @@
|
||||
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,
|
||||
};
|
||||
|
||||
export const dbConnect = async () => {
|
||||
if (mongoConnection.isConnected === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mongoose.connections.length > 0) {
|
||||
mongoConnection.isConnected = mongoose.connections[0].readyState;
|
||||
if (mongoConnection.isConnected === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
await mongoose.connect(MONGO_URL ?? '');
|
||||
mongoConnection.isConnected = 1;
|
||||
};
|
||||
|
||||
export const dbDisconnect = async () => {
|
||||
if (process.env.NODE_ENV === 'development') return;
|
||||
if (mongoConnection.isConnected === 0) return;
|
||||
|
||||
await mongoose.disconnect();
|
||||
mongoConnection.isConnected = 0;
|
||||
};
|
25
src/lib/models/Payment.ts
Normal file
25
src/lib/models/Payment.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const PaymentSchema = new mongoose.Schema(
|
||||
{
|
||||
type: {type: String, required: true, enum: ['payment', 'reimbursement']},
|
||||
name: {type: String, required: true},
|
||||
category : {type: String, required: false,},
|
||||
date: {type: Date, default: Date.now},
|
||||
images: [ {
|
||||
mediapath: {type: String, required: false},
|
||||
}],
|
||||
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.model("Payment", PaymentSchema);
|
96
src/routes/api/cospend/+server.ts
Normal file
96
src/routes/api/cospend/+server.ts
Normal file
@ -0,0 +1,96 @@
|
||||
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';
|
||||
|
||||
const UPLOAD_DIR = '/var/lib/www/static/test';
|
||||
const BASE_CURRENCY = 'CHF'; // Default currency
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
try {
|
||||
const name = formData.get('name') as string;
|
||||
const category = formData.get('category') as string;
|
||||
const date= new Date(formData.get('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[];
|
||||
let currency = formData.get('currency') as string;
|
||||
let original_amount = parseFloat(formData.get('original_amount') as string);
|
||||
let total_amount = NaN;
|
||||
|
||||
// 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 = parseFloat(formData.get('total_amount') as string);
|
||||
} else {
|
||||
const date_fmt = date.toISOString().split('T')[0]; // Convert date to YYYY-MM-DD format
|
||||
// Fetch conversion rate logic here (not implemented in this example)
|
||||
const res = await fetch(`https://api.frankfurter.app/${date_fmt}?from=${currency}&to=${BASE_CURRENCY}`)
|
||||
const { result } = await res.json();
|
||||
if (!result || !result[BASE_CURRENCY]) {
|
||||
return new Response(JSON.stringify({ message: 'Currency conversion failed.' }), { status: 400 });
|
||||
}
|
||||
// Assuming you want to convert the total amount to BASE_CURRENCY
|
||||
const conversionRate = parseFloat(result.rates[BASE_CURRENCY]);
|
||||
alert(`Conversion rate from ${currency} to ${BASE_CURRENCY} on ${date_fmt}: ${conversionRate}`);
|
||||
total_amount = original_amount * conversionRate;
|
||||
}
|
||||
|
||||
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({
|
||||
name,
|
||||
category,
|
||||
date,
|
||||
description,
|
||||
note,
|
||||
tags,
|
||||
total_amount,
|
||||
original_amount,
|
||||
currency,
|
||||
personal_amounts,
|
||||
images
|
||||
});
|
||||
|
||||
|
||||
// let auth = await locals.auth();
|
||||
// // if(!auth){
|
||||
// throw error(401, "Not logged in")
|
||||
// }
|
||||
|
||||
try{
|
||||
await Payment.create(payment);
|
||||
} catch(e){
|
||||
throw error(400, e)
|
||||
}
|
||||
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 });
|
||||
}
|
||||
};
|
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>
|
Loading…
x
Reference in New Issue
Block a user