wip
This commit is contained in:
20
frontend/src/app.d.ts
vendored
20
frontend/src/app.d.ts
vendored
@@ -51,7 +51,6 @@ interface User {
|
||||
last_name: string | '';
|
||||
password: string | '';
|
||||
phone: string | '';
|
||||
notes: string | '';
|
||||
address: string | '';
|
||||
zip_code: string | '';
|
||||
city: string | '';
|
||||
@@ -60,11 +59,9 @@ interface User {
|
||||
role_id: number | -1;
|
||||
dateofbirth: string | '';
|
||||
company: string | '';
|
||||
profile_picture: string | '';
|
||||
payment_status: number | -1;
|
||||
membership: Membership;
|
||||
bank_account: BankAccount;
|
||||
licence: Licence;
|
||||
membership: Membership | null;
|
||||
bank_account: BankAccount | null;
|
||||
licence: Licence | null;
|
||||
notes: string | '';
|
||||
}
|
||||
|
||||
@@ -80,9 +77,9 @@ interface Car {
|
||||
end_date: string | '';
|
||||
color: string | '';
|
||||
licence_plate: string | '';
|
||||
location: Location;
|
||||
damages: Damage[] | [];
|
||||
insurances: Insurance[] | [];
|
||||
location: Location | null;
|
||||
damages: Damage[] | null;
|
||||
insurances: Insurance[] | null;
|
||||
notes: string | '';
|
||||
}
|
||||
|
||||
@@ -93,8 +90,11 @@ interface Location {
|
||||
|
||||
interface Damage {
|
||||
id: number | -1;
|
||||
opponent: User;
|
||||
name: string | '';
|
||||
opponent: User | null;
|
||||
driver_id: number | -1;
|
||||
insurance: Insurance | null;
|
||||
date: string | '';
|
||||
notes: string | '';
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
import { applyAction, enhance } from '$app/forms';
|
||||
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { defaultCar } from '$lib/utils/defaults';
|
||||
import { defaultDamage, defaultInsurance, defaultOpponent } from '$lib/utils/defaults';
|
||||
import { PERMISSIONS } from '$lib/utils/constants';
|
||||
import Modal from './Modal.svelte';
|
||||
import UserEditForm from './UserEditForm.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -16,19 +18,54 @@
|
||||
/** @type {App.Locals['user'] } */
|
||||
export let editor;
|
||||
|
||||
/** @type {App.Types['car'] | null} */
|
||||
/** @type {App.Locals['users'] } */
|
||||
export let users;
|
||||
|
||||
/** @type {App.Types['car']} */
|
||||
export let car;
|
||||
|
||||
console.log('Opening car modal with:', car);
|
||||
$: car = car || { ...defaultCar() };
|
||||
$: console.log(
|
||||
'damage.opponent changed:',
|
||||
car?.damages.map((d) => d.opponent)
|
||||
);
|
||||
$: console.log(
|
||||
'damage.insurance changed:',
|
||||
car?.damages.map((d) => d.insurance)
|
||||
);
|
||||
// TODO: Remove when working
|
||||
// $: if (car.damages.length > 0 && !car.damages.every((d) => d.insurance && d.opponent)) {
|
||||
// car.damages = car.damages.map((damage) => ({
|
||||
// ...damage,
|
||||
// insurance: damage.insurance ?? defaultInsurance(),
|
||||
// opponent: damage.opponent ?? defaultOpponent()
|
||||
// }));
|
||||
// }
|
||||
let initialized = false; // Prevents infinite loops
|
||||
|
||||
// Ensure damages have default values once `car` is loaded
|
||||
$: if (car && !initialized) {
|
||||
car = {
|
||||
...car,
|
||||
damages:
|
||||
car.damages?.map((damage) => ({
|
||||
...damage,
|
||||
insurance: damage.insurance ?? defaultInsurance(),
|
||||
opponent: damage.opponent ?? defaultOpponent()
|
||||
})) || []
|
||||
};
|
||||
initialized = true; // Prevents re-running
|
||||
}
|
||||
$: isLoading = car === undefined || editor === undefined;
|
||||
let isUpdating = false;
|
||||
let readonlyUser = !hasPrivilige(editor, PERMISSIONS.Update);
|
||||
|
||||
/** @type {number | null} */
|
||||
let editingUserIndex = null;
|
||||
|
||||
const TABS = ['car.car', 'insurance', 'car.damages'];
|
||||
let activeTab = TABS[0];
|
||||
|
||||
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
|
||||
/** @type {import('@sveltejs/kit').SubmitFunction} */
|
||||
const handleUpdate = async () => {
|
||||
isUpdating = true;
|
||||
return async ({ result }) => {
|
||||
@@ -47,7 +84,7 @@
|
||||
<SmallLoader width={30} message={$t('loading.car_data')} />
|
||||
{:else if editor && car}
|
||||
<form class="content" action="?/updateCar" method="POST" use:enhance={handleUpdate}>
|
||||
<input name="susbscription[id]" type="hidden" bind:value={car.id} />
|
||||
<input name="car[id]" type="hidden" bind:value={car.id} />
|
||||
<h1 class="step-title" style="text-align: center;">
|
||||
{car.id ? $t('car.edit') : $t('car.create')}
|
||||
</h1>
|
||||
@@ -155,48 +192,357 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="tab-content" style="display: {activeTab === 'insurance' ? 'block' : 'none'}">
|
||||
{#each car.insurances as insurance}
|
||||
<InputField
|
||||
name="car[insurance][company]"
|
||||
label={$t('company')}
|
||||
bind:value={insurance.company}
|
||||
placeholder={$t('placeholder.company')}
|
||||
required={true}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurance][reference]"
|
||||
label={$t('insurance.reference')}
|
||||
bind:value={insurance.reference}
|
||||
placeholder={$t('placeholder.insurance_reference')}
|
||||
required={true}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurance][start_date]"
|
||||
type="date"
|
||||
label={$t('start')}
|
||||
bind:value={insurance.start_date}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurance][end_date]"
|
||||
type="date"
|
||||
label={$t('end')}
|
||||
bind:value={insurance.end_date}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurance][notes]"
|
||||
type="textarea"
|
||||
label={$t('notes')}
|
||||
bind:value={insurance.notes}
|
||||
placeholder={$t('placeholder.notes', {
|
||||
values: { name: insurance.company || '' }
|
||||
})}
|
||||
rows={10}
|
||||
/>
|
||||
{/each}
|
||||
<div class="accordion">
|
||||
{#each car.insurances as insurance, index}
|
||||
<input hidden value={insurance?.id} name="car[insurances][{index}][id]" />
|
||||
<details class="accordion-item" open={index === car.insurances.length - 1}>
|
||||
<summary class="accordion-header">
|
||||
{insurance.company ? insurance.company : ''}
|
||||
{insurance.reference ? ' (' + insurance.reference + ')' : ''}
|
||||
</summary>
|
||||
<div class="accordion-content">
|
||||
<InputField
|
||||
name="car[insurances][{index}][company]"
|
||||
label={$t('company')}
|
||||
bind:value={insurance.company}
|
||||
placeholder={$t('placeholder.company')}
|
||||
required={true}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurances][{index}][reference]"
|
||||
label={$t('insurance_reference')}
|
||||
bind:value={insurance.reference}
|
||||
placeholder={$t('placeholder.insurance_reference')}
|
||||
required={true}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurances][{index}][start_date]"
|
||||
type="date"
|
||||
label={$t('start')}
|
||||
bind:value={insurance.start_date}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurances][{index}][end_date]"
|
||||
type="date"
|
||||
label={$t('end')}
|
||||
bind:value={insurance.end_date}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[insurances][{index}][notes]"
|
||||
type="textarea"
|
||||
label={$t('notes')}
|
||||
bind:value={insurance.notes}
|
||||
placeholder={$t('placeholder.notes', {
|
||||
values: { name: insurance.company || '' }
|
||||
})}
|
||||
rows={10}
|
||||
/>
|
||||
{#if hasPrivilige(editor, PERMISSIONS.Delete)}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-delete danger"
|
||||
on:click={() => {
|
||||
if (
|
||||
confirm(
|
||||
$t('dialog.insurance_deletion', {
|
||||
values: {
|
||||
name: insurance.company + ' (' + insurance.reference + ')'
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
car.insurances = car.insurances.filter((_, i) => i !== index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="button-group">
|
||||
{#if hasPrivilige(editor, PERMISSIONS.Create)}
|
||||
<button
|
||||
type="button"
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
car.insurances = [...car.insurances, defaultInsurance()];
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-plus"></i>
|
||||
{$t('add_new')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content" style="display: {activeTab === 'car.damages' ? 'block' : 'none'}">
|
||||
<div class="accordion">
|
||||
{#each car.damages as damage, index (damage.id)}
|
||||
<input type="hidden" name="car[damages][{index}][id]" value={damage.id} />
|
||||
<details class="accordion-item" open={index === car.damages.length - 1}>
|
||||
<summary class="accordion-header">
|
||||
<span class="nav-badge">
|
||||
{damage.name} -
|
||||
{damage.opponent.first_name}
|
||||
{damage.opponent.last_name}
|
||||
</span>
|
||||
</summary>
|
||||
<div class="accordion-content">
|
||||
<InputField
|
||||
name="car[damages][{index}][date]"
|
||||
type="date"
|
||||
label={$t('date')}
|
||||
bind:value={damage.date}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[damages][{index}][name]"
|
||||
label={$t('car.damages')}
|
||||
bind:value={damage.name}
|
||||
required={true}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[damages][{index}][driver_id]"
|
||||
type="select"
|
||||
label={$t('user.member')}
|
||||
options={users
|
||||
?.filter((u) => u.role_id > 0)
|
||||
.map((u) => ({
|
||||
value: u.id,
|
||||
label: `${u.first_name} ${u.last_name}`,
|
||||
color: '--subtext1'
|
||||
})) || []}
|
||||
bind:value={damage.driver_id}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<h4>{$t('user.opponent')}</h4>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][id]`}
|
||||
value={car.damages[index].opponent.id}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][email]`}
|
||||
value={car.damages[index].opponent.email}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][first_name]`}
|
||||
value={car.damages[index].opponent.first_name}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][last_name]`}
|
||||
value={damage.opponent.last_name}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][phone]`}
|
||||
value={damage.opponent.phone}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][address]`}
|
||||
value={damage.opponent.address}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][city]`}
|
||||
value={damage.opponent.city}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][zip_code]`}
|
||||
value={damage.opponent.zip_code}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][notes]`}
|
||||
value={damage.opponent.notes}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][role_id]`}
|
||||
value={damage.opponent.role_id}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][status]`}
|
||||
value={damage.opponent.status}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][dateofbirth]`}
|
||||
value={damage.opponent.dateofbirth}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][company]`}
|
||||
value={damage.opponent.company}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][id]`}
|
||||
value={damage.opponent.bank_account.id}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][mandate_date_signed]`}
|
||||
value={damage.opponent.bank_account.mandate_date_signed}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][bank]`}
|
||||
value={damage.opponent.bank_account.bank}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][account_holder_name]`}
|
||||
value={damage.opponent.bank_account.account_holder_name}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][iban]`}
|
||||
value={damage.opponent.bank_account.iban}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][bic]`}
|
||||
value={damage.opponent.bank_account.bic}
|
||||
/>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][opponent][bank_account][mandate_reference]`}
|
||||
value={damage.opponent.bank_account.mandate_reference}
|
||||
/>
|
||||
<details class="accordion-item">
|
||||
<summary class="accordion-header">
|
||||
<span class="nav-badge">
|
||||
{#if damage.opponent?.first_name}
|
||||
{damage.opponent.first_name} {damage.opponent.last_name}
|
||||
{:else}
|
||||
{$t('not_set')}
|
||||
{/if}
|
||||
</span>
|
||||
</summary>
|
||||
<div class="accordion-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{$t('email')}</th>
|
||||
<td>{damage.opponent?.email || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('phone')}</th>
|
||||
<td>{damage.opponent?.phone || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('address')}</th>
|
||||
<td>{damage.opponent?.address || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('city')}</th>
|
||||
<td>{damage.opponent?.city || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('zip_code')}</th>
|
||||
<td>{damage.opponent?.zip_code || '-'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="button-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
if (!damage.opponent) {
|
||||
damage.opponent = defaultOpponent();
|
||||
}
|
||||
editingUserIndex = index;
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
{damage.opponent?.id ? $t('edit') : $t('edit')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<input
|
||||
hidden
|
||||
name={`car[damages][${index}][insurance][id]`}
|
||||
value={damage.insurance.id}
|
||||
/>
|
||||
<input hidden name={`car[damages][${index}][insurance][start_date]`} value="" />
|
||||
<input hidden name={`car[damages][${index}][insurance][end_date]`} value="" />
|
||||
<InputField
|
||||
name="car[damages][{index}][insurance][company]"
|
||||
label={$t('insurance')}
|
||||
bind:value={damage.insurance.company}
|
||||
placeholder={$t('placeholder.company')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[damages][{index}][insurance][reference]"
|
||||
label={$t('insurance_reference')}
|
||||
bind:value={damage.insurance.reference}
|
||||
placeholder={$t('placeholder.insurance_reference')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="car[damages][{index}][notes]"
|
||||
type="textarea"
|
||||
label={$t('notes')}
|
||||
bind:value={damage.notes}
|
||||
placeholder={$t('placeholder.notes')}
|
||||
rows={10}
|
||||
/>
|
||||
{#if hasPrivilige(editor, PERMISSIONS.Delete)}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-delete danger"
|
||||
on:click={() => {
|
||||
if (
|
||||
confirm(
|
||||
$t('dialog.damage_deletion', {
|
||||
values: {
|
||||
name: damage.name
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
car.damages = car.damages.filter((_, i) => i !== index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
{#if hasPrivilige(editor, PERMISSIONS.Create)}
|
||||
<button
|
||||
type="button"
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
car.damages = [...car.damages, defaultDamage()];
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-plus"></i>
|
||||
{$t('add_new')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="button-container">
|
||||
{#if isUpdating}
|
||||
@@ -211,7 +557,81 @@
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
{#if editingUserIndex !== null}
|
||||
<Modal on:close={close}>
|
||||
<UserEditForm
|
||||
{form}
|
||||
submit_form={false}
|
||||
subscriptions={null}
|
||||
licence_categories={null}
|
||||
{editor}
|
||||
bind:user={car.damages[editingUserIndex].opponent}
|
||||
on:cancel={() => (editingUserIndex = null)}
|
||||
on:close={() => {
|
||||
car.damages = car.damages;
|
||||
editingUserIndex = null;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.accordion-item {
|
||||
border: none;
|
||||
background: var(--surface0);
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.accordion-header {
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
color: var(--text);
|
||||
background: var(--surface1);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.accordion-header:hover {
|
||||
background: var(--surface2);
|
||||
}
|
||||
|
||||
.accordion-content {
|
||||
padding: 1rem;
|
||||
background: var(--surface0);
|
||||
border-top: 1px solid var(--surface1);
|
||||
}
|
||||
|
||||
.accordion-content .table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
.accordion-content .table th,
|
||||
.accordion-content .table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #2f2f2f;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.accordion-content .table th {
|
||||
color: var(--subtext1);
|
||||
}
|
||||
|
||||
.accordion-content .table td {
|
||||
color: var(--text);
|
||||
}
|
||||
.button-container button.active {
|
||||
background-color: var(--mauve);
|
||||
border-color: var(--mauve);
|
||||
color: var(--base);
|
||||
}
|
||||
.btn-delete {
|
||||
margin-left: auto;
|
||||
}
|
||||
.tab-content {
|
||||
padding: 1rem;
|
||||
border-radius: 0 0 3px 3px;
|
||||
@@ -219,6 +639,16 @@
|
||||
border: 1px solid var(--surface1);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.tab-content h4 {
|
||||
text-align: center;
|
||||
padding: 0.75rem;
|
||||
margin: 1rem 0;
|
||||
color: var(--lavender);
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
let inputValue = target.value;
|
||||
if (toUpperCase) {
|
||||
inputValue = inputValue.toUpperCase();
|
||||
target.value = inputValue; // Update the input field value
|
||||
}
|
||||
value = inputValue;
|
||||
target.value = inputValue; // Update the input field value
|
||||
value = inputValue.trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<form class="content" action="?/updateSubscription" method="POST" use:enhance={handleUpdate}>
|
||||
<input name="susbscription[id]" type="hidden" bind:value={subscription.id} />
|
||||
<h1 class="step-title" style="text-align: center;">
|
||||
{subscription.id ? $t('subscription.edit') : $t('subscription.create')}
|
||||
{subscription.id ? $t('subscriptions.edit') : $t('subscriptions.create')}
|
||||
</h1>
|
||||
{#if form?.errors}
|
||||
{#each form?.errors as error (error.id)}
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="tab-content" style="display: block">
|
||||
<InputField
|
||||
name="subscription[name]"
|
||||
label={$t('subscription.name')}
|
||||
label={$t('subscriptions.name')}
|
||||
bind:value={subscription.name}
|
||||
placeholder={$t('placeholder.subscription_name')}
|
||||
required={true}
|
||||
@@ -77,7 +77,7 @@
|
||||
<InputField
|
||||
name="subscription[conditions]"
|
||||
type="textarea"
|
||||
label={$t('subscription.conditions')}
|
||||
label={$t('subscriptions.conditions')}
|
||||
bind:value={subscription.conditions}
|
||||
placeholder={$t('placeholder.subscription_conditions')}
|
||||
readonly={subscription.id > 0}
|
||||
@@ -85,7 +85,7 @@
|
||||
<InputField
|
||||
name="subscription[monthly_fee]"
|
||||
type="number"
|
||||
label={$t('subscription.monthly_fee')}
|
||||
label={$t('subscriptions.monthly_fee')}
|
||||
bind:value={subscription.monthly_fee}
|
||||
placeholder={$t('placeholder.subscription_monthly_fee')}
|
||||
required={true}
|
||||
@@ -94,7 +94,7 @@
|
||||
<InputField
|
||||
name="subscription[hourly_rate]"
|
||||
type="number"
|
||||
label={$t('subscription.hourly_rate')}
|
||||
label={$t('subscriptions.hourly_rate')}
|
||||
bind:value={subscription.hourly_rate}
|
||||
required={true}
|
||||
readonly={subscription.id > 0}
|
||||
@@ -102,14 +102,14 @@
|
||||
<InputField
|
||||
name="subscription[included_hours_per_year]"
|
||||
type="number"
|
||||
label={$t('subscription.included_hours_per_year')}
|
||||
label={$t('subscriptions.included_hours_per_year')}
|
||||
bind:value={subscription.included_hours_per_year}
|
||||
readonly={subscription.id > 0}
|
||||
/>
|
||||
<InputField
|
||||
name="included_hours_per_month"
|
||||
type="number"
|
||||
label={$t('subscription.included_hours_per_month')}
|
||||
label={$t('subscriptions.included_hours_per_month')}
|
||||
bind:value={subscription.included_hours_per_month}
|
||||
readonly={subscription.id > 0}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { PERMISSIONS } from '$lib/utils/constants';
|
||||
import { defaultLicence } from '$lib/utils/defaults';
|
||||
// import { defaultBankAccount, defaultLicence, defaultMembership } from '$lib/utils/defaults';
|
||||
|
||||
/** @type {import('../../routes/auth/about/[id]/$types').ActionData} */
|
||||
export let form;
|
||||
@@ -20,10 +20,15 @@
|
||||
export let submit_form = true;
|
||||
|
||||
// Ensure licence is initialized before passing to child
|
||||
$: if (user && !user.licence) {
|
||||
user.licence = defaultLicence();
|
||||
}
|
||||
|
||||
// $: if (user && !user.licence) {
|
||||
// user.licence = defaultLicence();
|
||||
// }
|
||||
// $: if (user && !user.membership) {
|
||||
// user.membership = defaultMembership();
|
||||
// }
|
||||
// $: if (user && !user.bank_account) {
|
||||
// user.bank_account = defaultBankAccount();
|
||||
// }
|
||||
/** @type {App.Locals['user']} */
|
||||
export let editor;
|
||||
|
||||
@@ -31,7 +36,9 @@
|
||||
|
||||
// $: isNewUser = user === null;
|
||||
$: isLoading = user === undefined;
|
||||
|
||||
$: if (user != null) {
|
||||
console.log(user);
|
||||
}
|
||||
/** @type {App.Locals['licence_categories'] | null} */
|
||||
export let licence_categories;
|
||||
|
||||
@@ -43,6 +50,7 @@
|
||||
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
||||
];
|
||||
const userRoleOptions = [
|
||||
{ value: -1, label: $t('userRole.-1'), color: '--red' }, // Red for "Opponent"
|
||||
{ value: 0, label: $t('userRole.0'), color: '--subtext1' }, // Grey for "Nicht verifiziert"
|
||||
{ value: 1, label: $t('userRole.1'), color: '--light-green' }, // Light green for "Verifiziert"
|
||||
{ value: 2, label: $t('userRole.2'), color: '--green' }, // Light green for "Verifiziert"
|
||||
@@ -62,14 +70,10 @@
|
||||
];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const TABS = [
|
||||
'profile',
|
||||
'bankaccount',
|
||||
...(hasPrivilige(user, PERMISSIONS.Member) ? 'membership' : []),
|
||||
...(user.licence ? 'licence' : [])
|
||||
];
|
||||
/** @type { (keyof user)[] } */
|
||||
const TABS = ['membership', 'licence', 'bank_account'];
|
||||
|
||||
let activeTab = TABS[0];
|
||||
let activeTab = 'profile';
|
||||
|
||||
let isUpdating = false,
|
||||
password = '',
|
||||
@@ -77,13 +81,13 @@
|
||||
|
||||
/** @type {Object.<string, App.Locals['licence_categories']>} */
|
||||
$: groupedCategories = licence_categories ? groupCategories(licence_categories) : {};
|
||||
$: subscriptionModelOptions = subscriptions
|
||||
$: subscriptionOptions = subscriptions
|
||||
? subscriptions.map((sub) => ({
|
||||
value: sub?.name ?? '',
|
||||
label: sub?.name ?? ''
|
||||
}))
|
||||
: [];
|
||||
$: selectedSubscriptionModel = subscriptions
|
||||
$: selectedSubscription = subscriptions
|
||||
? subscriptions.find((sub) => sub?.name === user.membership?.subscription.name) || null
|
||||
: null;
|
||||
/**
|
||||
@@ -172,26 +176,38 @@
|
||||
{/if}
|
||||
|
||||
<div class="button-container">
|
||||
<button
|
||||
type="button"
|
||||
class="button-dark"
|
||||
class:active={activeTab === 'profile'}
|
||||
on:click={() => (activeTab = 'profile')}
|
||||
>
|
||||
{$t('profile')}
|
||||
</button>
|
||||
{#each TABS as tab}
|
||||
<button
|
||||
type="button"
|
||||
class="button-dark"
|
||||
class:active={activeTab === tab}
|
||||
on:click={() => (activeTab = tab)}
|
||||
>
|
||||
{$t(tab)}
|
||||
</button>
|
||||
{#if user[tab] != null}
|
||||
<button
|
||||
type="button"
|
||||
class="button-dark"
|
||||
class:active={activeTab === tab}
|
||||
on:click={() => (activeTab = tab)}
|
||||
>
|
||||
{$t('user.' + tab)}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="tab-content" style="display: {activeTab === 'profile' ? 'block' : 'none'}">
|
||||
<InputField
|
||||
name="user[status]"
|
||||
type="select"
|
||||
label={$t('status')}
|
||||
bind:value={user.status}
|
||||
options={userStatusOptions}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||
<InputField
|
||||
name="user[status]"
|
||||
type="select"
|
||||
label={$t('status')}
|
||||
bind:value={user.status}
|
||||
options={userStatusOptions}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasPrivilige(editor, PERMISSIONS.Super)}
|
||||
<InputField
|
||||
name="user[role_id]"
|
||||
@@ -367,133 +383,137 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="tab-content"
|
||||
style="display: {activeTab === 'membership' && subscriptions ? 'block' : 'none'}"
|
||||
>
|
||||
<InputField
|
||||
name="user[membership][status]"
|
||||
type="select"
|
||||
label={$t('status')}
|
||||
bind:value={user.membership.status}
|
||||
options={membershipStatusOptions}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="user[membership][subscription][name]"
|
||||
type="select"
|
||||
label={$t('subscriptions.subscription')}
|
||||
bind:value={user.membership.subscription.name}
|
||||
options={subscriptionModelOptions}
|
||||
readonly={readonlyUser || !hasPrivilige(user, PERMISSIONS.Member)}
|
||||
/>
|
||||
<div class="subscription-info">
|
||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||
{#if user.membership}
|
||||
<div
|
||||
class="tab-content"
|
||||
style="display: {activeTab === 'membership' && subscriptions ? 'block' : 'none'}"
|
||||
>
|
||||
<InputField
|
||||
name="user[membership][status]"
|
||||
type="select"
|
||||
label={$t('status')}
|
||||
bind:value={user.membership.status}
|
||||
options={membershipStatusOptions}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="user[membership][subscription][name]"
|
||||
type="select"
|
||||
label={$t('subscriptions.subscription')}
|
||||
bind:value={user.membership.subscription.name}
|
||||
options={subscriptionOptions}
|
||||
readonly={readonlyUser || !hasPrivilige(user, PERMISSIONS.Member)}
|
||||
/>
|
||||
<div class="subscription-info">
|
||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||
<div class="subscription-column">
|
||||
<p>
|
||||
<strong>{$t('subscriptions.monthly_fee')}:</strong>
|
||||
{selectedSubscription?.monthly_fee || '-'} €
|
||||
</p>
|
||||
<p>
|
||||
<strong>{$t('subscriptions.hourly_rate')}:</strong>
|
||||
{selectedSubscription?.hourly_rate || '-'} €
|
||||
</p>
|
||||
{#if selectedSubscription?.included_hours_per_year}
|
||||
<p>
|
||||
<strong>{$t('subscriptions.included_hours_per_year')}:</strong>
|
||||
{selectedSubscription?.included_hours_per_year}
|
||||
</p>
|
||||
{/if}
|
||||
{#if selectedSubscription?.included_hours_per_month}
|
||||
<p>
|
||||
<strong>{$t('subscriptions.included_hours_per_month')}:</strong>
|
||||
{selectedSubscription?.included_hours_per_month}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="subscription-column">
|
||||
<p>
|
||||
<strong>{$t('subscriptions.monthly_fee')}:</strong>
|
||||
{selectedSubscriptionModel?.monthly_fee || '-'} €
|
||||
<strong>{$t('details')}:</strong>
|
||||
{selectedSubscription?.details || '-'}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{$t('subscriptions.hourly_rate')}:</strong>
|
||||
{selectedSubscriptionModel?.hourly_rate || '-'} €
|
||||
</p>
|
||||
{#if selectedSubscriptionModel?.included_hours_per_year}
|
||||
{#if selectedSubscription?.conditions}
|
||||
<p>
|
||||
<strong>{$t('subscriptions.included_hours_per_year')}:</strong>
|
||||
{selectedSubscriptionModel?.included_hours_per_year}
|
||||
</p>
|
||||
{/if}
|
||||
{#if selectedSubscriptionModel?.included_hours_per_month}
|
||||
<p>
|
||||
<strong>{$t('subscriptions.included_hours_per_month')}:</strong>
|
||||
{selectedSubscriptionModel?.included_hours_per_month}
|
||||
<strong>{$t('subscriptions.conditions')}:</strong>
|
||||
{selectedSubscription?.conditions}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="subscription-column">
|
||||
<p>
|
||||
<strong>{$t('details')}:</strong>
|
||||
{selectedSubscriptionModel?.details || '-'}
|
||||
</p>
|
||||
{#if selectedSubscriptionModel?.conditions}
|
||||
<p>
|
||||
<strong>{$t('subscriptions.conditions')}:</strong>
|
||||
{selectedSubscriptionModel?.conditions}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<InputField
|
||||
name="user[membership][start_date]"
|
||||
type="date"
|
||||
label={$t('start')}
|
||||
bind:value={user.membership.start_date}
|
||||
placeholder={$t('placeholder.start_date')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="user[membership][end_date]"
|
||||
type="date"
|
||||
label={$t('end')}
|
||||
bind:value={user.membership.end_date}
|
||||
placeholder={$t('placeholder.end_date')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||
<InputField
|
||||
name="user[membership][parent_member_id]"
|
||||
type="number"
|
||||
label={$t('parent_member_id')}
|
||||
bind:value={user.membership.parent_member_id}
|
||||
placeholder={$t('placeholder.parent_member_id')}
|
||||
name="user[membership][start_date]"
|
||||
type="date"
|
||||
label={$t('start')}
|
||||
bind:value={user.membership.start_date}
|
||||
placeholder={$t('placeholder.start_date')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="tab-content" style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}">
|
||||
<InputField
|
||||
name="user[bank_account][account_holder_name]"
|
||||
label={$t('bank_account_holder')}
|
||||
bind:value={user.bank_account.account_holder_name}
|
||||
placeholder={$t('placeholder.bank_account_holder')}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][bank_name]"
|
||||
label={$t('bank_name')}
|
||||
bind:value={user.bank_account.bank}
|
||||
placeholder={$t('placeholder.bank_name')}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][iban]"
|
||||
label={$t('iban')}
|
||||
bind:value={user.bank_account.iban}
|
||||
placeholder={$t('placeholder.iban')}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][bic]"
|
||||
label={$t('bic')}
|
||||
bind:value={user.bank_account.bic}
|
||||
placeholder={$t('placeholder.bic')}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][mandate_reference]"
|
||||
label={$t('mandate_reference')}
|
||||
bind:value={user.bank_account.mandate_reference}
|
||||
placeholder={$t('placeholder.mandate_reference')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][mandate_date_signed]"
|
||||
label={$t('mandate_date_signed')}
|
||||
type="date"
|
||||
bind:value={user.bank_account.mandate_date_signed}
|
||||
readonly={true}
|
||||
/>
|
||||
</div>
|
||||
<InputField
|
||||
name="user[membership][end_date]"
|
||||
type="date"
|
||||
label={$t('end')}
|
||||
bind:value={user.membership.end_date}
|
||||
placeholder={$t('placeholder.end_date')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||
<InputField
|
||||
name="user[membership][parent_member_id]"
|
||||
type="number"
|
||||
label={$t('parent_member_id')}
|
||||
bind:value={user.membership.parent_member_id}
|
||||
placeholder={$t('placeholder.parent_member_id')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if user.bank_account}
|
||||
<div class="tab-content" style="display: {activeTab === 'bank_account' ? 'block' : 'none'}">
|
||||
<InputField
|
||||
name="user[bank_account][account_holder_name]"
|
||||
label={$t('bank_account_holder')}
|
||||
bind:value={user.bank_account.account_holder_name}
|
||||
placeholder={$t('placeholder.bank_account_holder')}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][bank_name]"
|
||||
label={$t('bank_name')}
|
||||
bind:value={user.bank_account.bank}
|
||||
placeholder={$t('placeholder.bank_name')}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][iban]"
|
||||
label={$t('iban')}
|
||||
bind:value={user.bank_account.iban}
|
||||
placeholder={$t('placeholder.iban')}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][bic]"
|
||||
label={$t('bic')}
|
||||
bind:value={user.bank_account.bic}
|
||||
placeholder={$t('placeholder.bic')}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][mandate_reference]"
|
||||
label={$t('mandate_reference')}
|
||||
bind:value={user.bank_account.mandate_reference}
|
||||
placeholder={$t('placeholder.mandate_reference')}
|
||||
readonly={readonlyUser}
|
||||
/>
|
||||
<InputField
|
||||
name="user[bank_account][mandate_date_signed]"
|
||||
label={$t('mandate_date_signed')}
|
||||
type="date"
|
||||
bind:value={user.bank_account.mandate_date_signed}
|
||||
readonly={true}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button-container">
|
||||
{#if isUpdating}
|
||||
<SmallLoader width={30} message={$t('loading.updating')} />
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
5: 'Passiv'
|
||||
},
|
||||
userRole: {
|
||||
'-5': 'Unfallgegner',
|
||||
'-1': 'Unfallgegner',
|
||||
0: 'Sponsor',
|
||||
1: 'Mitglied',
|
||||
2: 'Betrachter',
|
||||
@@ -131,6 +131,7 @@ export default {
|
||||
edit: 'Nutzer bearbeiten',
|
||||
create: 'Nutzer erstellen',
|
||||
user: 'Nutzer',
|
||||
member: 'Mitglied',
|
||||
management: 'Mitgliederverwaltung',
|
||||
id: 'Mitgliedsnr',
|
||||
first_name: 'Vorname',
|
||||
@@ -138,9 +139,12 @@ export default {
|
||||
phone: 'Telefonnummer',
|
||||
dateofbirth: 'Geburtstag',
|
||||
email: 'Email',
|
||||
membership: 'Mitgliedschaft',
|
||||
bank_account: 'Kontodaten',
|
||||
status: 'Status',
|
||||
role: 'Nutzerrolle',
|
||||
supporter: 'Sponsor'
|
||||
supporter: 'Sponsor',
|
||||
opponent: 'Unfallgegner'
|
||||
},
|
||||
subscriptions: {
|
||||
name: 'Modellname',
|
||||
@@ -186,8 +190,11 @@ export default {
|
||||
actions: 'Aktionen',
|
||||
edit: 'Bearbeiten',
|
||||
delete: 'Löschen',
|
||||
not_set: 'Nicht gesetzt',
|
||||
noone: 'Niemand',
|
||||
search: 'Suche:',
|
||||
name: 'Name',
|
||||
date: 'Datum',
|
||||
price: 'Preis',
|
||||
color: 'Farbe',
|
||||
grant_backend_access: 'Backend Zugriff gewähren',
|
||||
@@ -221,8 +228,6 @@ export default {
|
||||
login: 'Anmeldung',
|
||||
profile: 'Profil',
|
||||
cars: 'Fahrzeuge',
|
||||
membership: 'Mitgliedschaft',
|
||||
bankaccount: 'Kontodaten',
|
||||
status: 'Status',
|
||||
start: 'Beginn',
|
||||
end: 'Ende',
|
||||
|
||||
@@ -182,7 +182,7 @@ export default {
|
||||
login: 'Login',
|
||||
profile: 'Profile',
|
||||
membership: 'Membership',
|
||||
bankaccount: 'Bank Account',
|
||||
bank_account: 'Bank Account',
|
||||
status: 'Status',
|
||||
start: 'Start',
|
||||
end: 'End',
|
||||
|
||||
@@ -10,6 +10,3 @@ export const PERMISSIONS = {
|
||||
Delete: 4,
|
||||
Super: 8
|
||||
};
|
||||
|
||||
export const SUPPORTER_SUBSCRIPTION_NAME = 'Keins';
|
||||
export const OPPONENT_SUBSCRIPTION_NAME = 'Keins';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// src/lib/utils/defaults.js
|
||||
|
||||
import { OPPONENT_SUBSCRIPTION_NAME, SUPPORTER_SUBSCRIPTION_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* @returns {App.Types['subscription']}
|
||||
*/
|
||||
@@ -79,8 +77,6 @@ export function defaultUser() {
|
||||
company: '',
|
||||
dateofbirth: '',
|
||||
notes: '',
|
||||
profile_picture: '',
|
||||
payment_status: 0,
|
||||
status: 1,
|
||||
role_id: 1,
|
||||
membership: defaultMembership(),
|
||||
@@ -97,7 +93,7 @@ export function defaultSupporter() {
|
||||
supporter.status = 5;
|
||||
supporter.role_id = 0;
|
||||
supporter.licence = null;
|
||||
supporter.membership.subscription.name = SUPPORTER_SUBSCRIPTION_NAME;
|
||||
supporter.membership = null;
|
||||
return supporter;
|
||||
}
|
||||
|
||||
@@ -109,7 +105,7 @@ export function defaultOpponent() {
|
||||
opponent.status = 5;
|
||||
opponent.role_id = -1;
|
||||
opponent.licence = null;
|
||||
opponent.membership.subscription.name = OPPONENT_SUBSCRIPTION_NAME;
|
||||
opponent.membership = null;
|
||||
return opponent;
|
||||
}
|
||||
/**
|
||||
@@ -128,8 +124,11 @@ export function defaultLocation() {
|
||||
export function defaultDamage() {
|
||||
return {
|
||||
id: 0,
|
||||
opponent: defaultUser(),
|
||||
insurance: null,
|
||||
name: '',
|
||||
opponent: defaultOpponent(),
|
||||
driver_id: -1,
|
||||
insurance: defaultInsurance(),
|
||||
date: '',
|
||||
notes: ''
|
||||
};
|
||||
}
|
||||
@@ -155,7 +154,7 @@ export function defaultCar() {
|
||||
return {
|
||||
id: 0,
|
||||
name: '',
|
||||
status: '',
|
||||
status: 0,
|
||||
brand: '',
|
||||
model: '',
|
||||
price: 0,
|
||||
|
||||
@@ -72,7 +72,7 @@ export function isEmpty(obj) {
|
||||
* @returns string
|
||||
*/
|
||||
export function toRFC3339(dateString) {
|
||||
if (!dateString) dateString = '0001-01-01T00:00:00.000Z';
|
||||
if (!dateString || dateString == '') dateString = '0001-01-01T00:00:00.000Z';
|
||||
const date = new Date(dateString);
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { defaultBankAccount, defaultMembership } from './defaults';
|
||||
import { toRFC3339 } from './helpers';
|
||||
|
||||
/**
|
||||
@@ -24,20 +25,18 @@ export function formDataToObject(formData) {
|
||||
|
||||
// console.log('Current object state:', JSON.stringify(current));
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
/**
|
||||
* Create nested object if it doesn't exist
|
||||
* @type {Record<string, any>}
|
||||
* @description Ensures proper nesting structure for user data fields
|
||||
* @example
|
||||
* // For input name="user[membership][status]"
|
||||
* // Creates: { user: { membership: { status: value } } }
|
||||
*/
|
||||
current[keys[i]] = current[keys[i]] || {};
|
||||
const currentKey = keys[i];
|
||||
const nextKey = keys[i + 1];
|
||||
const isNextKeyArrayIndex = !isNaN(Number(nextKey));
|
||||
if (!current[currentKey]) {
|
||||
// If next key is a number, initialize an array, otherwise an object
|
||||
current[currentKey] = isNextKeyArrayIndex ? [] : {};
|
||||
}
|
||||
/**
|
||||
* Move to the next level of the object
|
||||
* @type {Record<string, any>}
|
||||
*/
|
||||
current = current[keys[i]];
|
||||
current = current[currentKey];
|
||||
}
|
||||
|
||||
const lastKey = keys[keys.length - 1];
|
||||
@@ -50,7 +49,20 @@ export function formDataToObject(formData) {
|
||||
current[lastKey].push(value);
|
||||
}
|
||||
} else {
|
||||
current[lastKey] = value;
|
||||
if (Array.isArray(current)) {
|
||||
// If current is an array, lastKey should be the index
|
||||
const index = parseInt(lastKey);
|
||||
current[index] = current[index] || {};
|
||||
if (keys.length > 2) {
|
||||
// For nested properties within array elements
|
||||
const propertyKey = keys[keys.length - 1];
|
||||
current[index][propertyKey] = value;
|
||||
} else {
|
||||
current[index] = value;
|
||||
}
|
||||
} else {
|
||||
current[lastKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +70,9 @@ export function formDataToObject(formData) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the raw form data into the expected user data structure
|
||||
* @param {{ object: Partial<App.Locals['user']>, confirm_password: string} } rawData - The raw form data object
|
||||
* @returns {{ user: Partial<App.Locals['user']> }} Processed user data
|
||||
* Processes the raw form data into the expected membership data structure
|
||||
* @param { App.Types['membership'] } membership - The raw form data object
|
||||
* @returns {App.Types['membership']} Processed membership data
|
||||
*/
|
||||
export function processMembershipFormData(membership) {
|
||||
return {
|
||||
@@ -73,39 +85,71 @@ export function processMembershipFormData(membership) {
|
||||
};
|
||||
}
|
||||
|
||||
licence: {
|
||||
id: Number(rawData.object.licence?.id) || 0,
|
||||
status: Number(rawData.object.licence?.status),
|
||||
number: String(rawData.object.licence?.number || ''),
|
||||
issued_date: toRFC3339(String(rawData.object.licence?.issued_date || '')),
|
||||
expiration_date: toRFC3339(String(rawData.object.licence?.expiration_date || '')),
|
||||
country: String(rawData.object.licence?.country || ''),
|
||||
categories: rawData.object.licence?.categories || []
|
||||
},
|
||||
/**
|
||||
* Processes the raw form data into the expected licence data structure
|
||||
* @param { App.Types['licence'] } licence - The raw form data object
|
||||
* @returns {App.Types['licence']} Processed licence data
|
||||
*/
|
||||
export function processLicenceFormData(licence) {
|
||||
return {
|
||||
id: Number(licence?.id) || 0,
|
||||
status: Number(licence?.status),
|
||||
number: String(licence?.number || ''),
|
||||
issued_date: toRFC3339(String(licence?.issued_date || '')),
|
||||
expiration_date: toRFC3339(String(licence?.expiration_date || '')),
|
||||
country: String(licence?.country || ''),
|
||||
categories: licence?.categories || []
|
||||
};
|
||||
}
|
||||
|
||||
bank_account: {
|
||||
id: Number(rawData.object.bank_account?.id) || 0,
|
||||
account_holder_name: String(rawData.object.bank_account?.account_holder_name || ''),
|
||||
bank: String(rawData.object.bank_account?.bank || ''),
|
||||
iban: String(rawData.object.bank_account?.iban || ''),
|
||||
bic: String(rawData.object.bank_account?.bic || ''),
|
||||
mandate_reference: String(rawData.object.bank_account?.mandate_reference || ''),
|
||||
mandate_date_signed: toRFC3339(
|
||||
String(rawData.object.bank_account?.mandate_date_signed || '')
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Processes the raw form data into the expected bank_account data structure
|
||||
* @param { App.Types['bankAccount'] } bank_account - The raw form data object
|
||||
* @returns {App.Types['bankAccount']} Processed bank_account data
|
||||
*/
|
||||
export function processBankAccountFormData(bank_account) {
|
||||
{
|
||||
return {
|
||||
id: Number(bank_account?.id) || 0,
|
||||
account_holder_name: String(bank_account?.account_holder_name || ''),
|
||||
bank: String(bank_account?.bank || ''),
|
||||
iban: String(bank_account?.iban || ''),
|
||||
bic: String(bank_account?.bic || ''),
|
||||
mandate_reference: String(bank_account?.mandate_reference || ''),
|
||||
mandate_date_signed: toRFC3339(String(bank_account?.mandate_date_signed || ''))
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Processes the raw form data into the expected user data structure
|
||||
* @param { Partial<App.Locals['user']> } user - The raw form data object
|
||||
* @returns {App.Locals['user']} Processed user data
|
||||
*/
|
||||
export function processUserFormData(user) {
|
||||
/** @type {App.Locals['user']} */
|
||||
let processedData = {
|
||||
id: Number(user.id) || 0,
|
||||
status: Number(user.status),
|
||||
role_id: Number(user.role_id),
|
||||
first_name: String(user.first_name),
|
||||
last_name: String(user.last_name),
|
||||
password: String(user.password) || '',
|
||||
email: String(user.email),
|
||||
phone: String(user.phone || ''),
|
||||
company: String(user.company || ''),
|
||||
dateofbirth: toRFC3339(String(user.dateofbirth || '')),
|
||||
address: String(user.address || ''),
|
||||
zip_code: String(user.zip_code || ''),
|
||||
city: String(user.city || ''),
|
||||
notes: String(user.notes || ''),
|
||||
membership: processMembershipFormData(user.membership ? user.membership : defaultMembership()),
|
||||
licence: user.licence ? processLicenceFormData(user.licence) : null,
|
||||
bank_account: processBankAccountFormData(
|
||||
user.bank_account ? user.bank_account : defaultBankAccount()
|
||||
)
|
||||
};
|
||||
// console.log('Categories: --------');
|
||||
// console.dir(rawData.object.licence);
|
||||
if (
|
||||
rawData.object.password &&
|
||||
rawData.confirm_password &&
|
||||
rawData.object.password === rawData.confirm_password &&
|
||||
rawData.object.password.trim() !== ''
|
||||
) {
|
||||
processedData.user.password = rawData.object.password;
|
||||
}
|
||||
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
||||
value !== null && value !== '' ? value : undefined
|
||||
);
|
||||
@@ -136,39 +180,85 @@ export function processSubscriptionFormData(subscription) {
|
||||
console.dir(clean);
|
||||
return clean;
|
||||
}
|
||||
/**
|
||||
* Processes the raw form data into the expected insurance data structure
|
||||
* @param {App.Types['insurance']} insurance - The raw form data object
|
||||
* @returns {App.Types['insurance']} Processed user data
|
||||
*/
|
||||
export function processInsuranceFormData(insurance) {
|
||||
return {
|
||||
id: Number(insurance.id) || 0,
|
||||
company: String(insurance.company) || '',
|
||||
reference: String(insurance.reference) || '',
|
||||
start_date: toRFC3339(String(insurance.start_date) || '') || '',
|
||||
end_date: toRFC3339(String(insurance.end_date) || '') || '',
|
||||
notes: String(insurance.notes) || ''
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the raw form data into the expected car data structure
|
||||
* @param {{ object: Partial<App.Types['car']>, confirm_password: string }} rawData - The raw form data object
|
||||
* @returns {{ car: Partial<App.Types['car']> }} Processed user data
|
||||
* @param {Partial<App.Types['car']>} car - The raw form data object
|
||||
* @returns {App.Types['car']} Processed user data
|
||||
*/
|
||||
export function processCarFormData(rawData) {
|
||||
/** @type {{ car: Partial<App.Types['car']> }} */
|
||||
export function processCarFormData(car) {
|
||||
console.dir(car);
|
||||
/** @type {App.Types['car']} */
|
||||
let processedData = {
|
||||
car: {
|
||||
id: Number(rawData.object.id) || 0,
|
||||
name: String(rawData.object.name) || '',
|
||||
status: Number(rawData.object.status) || 0,
|
||||
brand: String(rawData.object.brand) || '',
|
||||
model: String(rawData.object.model) || '',
|
||||
price: Number(rawData.object.price) || 0,
|
||||
rate: Number(rawData.object.rate) || 0,
|
||||
licence_plate: String(rawData.object.licence_plate) || '',
|
||||
start_date: toRFC3339(String(rawData.object.start_date)) || '',
|
||||
end_date: toRFC3339(String(rawData.object.end_date)) || '',
|
||||
color: String(rawData.object.color) || '',
|
||||
notes: String(rawData.object.notes) || '',
|
||||
location: {
|
||||
latitude: Number(rawData.object.location?.latitude) || 0,
|
||||
longitude: Number(rawData.object.location?.longitude) || 0
|
||||
},
|
||||
damages: rawData.object.damages || [],
|
||||
insurances: rawData.object.insurances || []
|
||||
}
|
||||
id: Number(car.id) || 0,
|
||||
name: String(car.name) || '',
|
||||
status: Number(car.status) || 0,
|
||||
brand: String(car.brand) || '',
|
||||
model: String(car.model) || '',
|
||||
price: Number(car.price) || 0,
|
||||
rate: Number(car.rate) || 0,
|
||||
licence_plate: String(car.licence_plate),
|
||||
start_date: 'start_date' in car ? toRFC3339(String(car.start_date) || '') : '',
|
||||
end_date: 'end_date' in car ? toRFC3339(String(car.end_date) || '') : '',
|
||||
color: String(car.color) || '',
|
||||
notes: String(car.notes) || '',
|
||||
location:
|
||||
'location' in car
|
||||
? {
|
||||
latitude: Number(car.location?.latitude) || 0,
|
||||
longitude: Number(car.location?.longitude) || 0
|
||||
}
|
||||
: {
|
||||
latitude: 0,
|
||||
longitude: 0
|
||||
},
|
||||
damages: /** @type {App.Types['damage'][]} */ ([]),
|
||||
insurances: /** @type {App.Types['insurance'][]} */ ([])
|
||||
};
|
||||
car.insurances?.forEach((insurance) => {
|
||||
processedData.insurances.push(processInsuranceFormData(insurance));
|
||||
});
|
||||
|
||||
car.damages?.forEach((damage) => {
|
||||
console.dir(damage);
|
||||
processedData.damages.push(processDamageFormData(damage));
|
||||
});
|
||||
|
||||
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
||||
value !== null && value !== '' ? value : undefined
|
||||
);
|
||||
console.dir(clean);
|
||||
return clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the raw form data into the expected damage data structure
|
||||
* @param { App.Types['damage'] } damage - The raw form data object
|
||||
* @returns {App.Types['damage']} Processed damage data
|
||||
*/
|
||||
export function processDamageFormData(damage) {
|
||||
return {
|
||||
id: Number(damage.id) || 0,
|
||||
name: String(damage.name) || '',
|
||||
opponent: processUserFormData(damage.opponent),
|
||||
driver_id: Number(damage.driver_id) || 0,
|
||||
insurance: processInsuranceFormData(damage.insurance),
|
||||
date: toRFC3339(String(damage.date) || ''),
|
||||
notes: String(damage.notes) || ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,8 +30,21 @@ export const actions = {
|
||||
updateUser: async ({ request, fetch, cookies, locals }) => {
|
||||
let formData = await request.formData();
|
||||
|
||||
const rawData = formDataToObject(formData);
|
||||
const processedData = processUserFormData(rawData);
|
||||
const rawFormData = formDataToObject(formData);
|
||||
/** @type {{object: Partial<App.Locals['user']>, confirm_password: string}} */
|
||||
const rawData = {
|
||||
object: /** @type {Partial<App.Locals['user']>} */ (rawFormData.object),
|
||||
confirm_password: rawFormData.confirm_password
|
||||
};
|
||||
// confirm password matches and is not empty. Otherwise set password to empty string
|
||||
if (
|
||||
rawData.object.password &&
|
||||
rawData.confirm_password &&
|
||||
(rawData.object.password != rawData.confirm_password || rawData.object.password.trim() == '')
|
||||
) {
|
||||
rawData.object.password = '';
|
||||
}
|
||||
const processedData = processUserFormData(rawData.object);
|
||||
|
||||
// const isCreating = !processedData.user.id || processedData.user.id === 0;
|
||||
// console.log('Is creating: ', isCreating);
|
||||
|
||||
@@ -38,15 +38,23 @@ export const actions = {
|
||||
let formData = await request.formData();
|
||||
|
||||
const rawFormData = formDataToObject(formData);
|
||||
/** @type {{object: Partial<App.Locals['user']>, confirm_password: string}} */
|
||||
/** @type {{object: App.Locals['user'], confirm_password: string}} */
|
||||
const rawData = {
|
||||
object: /** @type {Partial<App.Locals['user']>} */ (rawFormData.object),
|
||||
object: /** @type {App.Locals['user']} */ (rawFormData.object),
|
||||
confirm_password: rawFormData.confirm_password
|
||||
};
|
||||
const processedData = processUserFormData(rawData);
|
||||
// confirm password matches and is not empty. Otherwise set password to empty string
|
||||
if (
|
||||
rawData.object.password &&
|
||||
rawData.confirm_password &&
|
||||
(rawData.object.password != rawData.confirm_password || rawData.object.password.trim() == '')
|
||||
) {
|
||||
rawData.object.password = '';
|
||||
}
|
||||
const user = processUserFormData(rawData.object);
|
||||
|
||||
console.dir(processedData.user.membership);
|
||||
const isCreating = !processedData.user.id || processedData.user.id === 0;
|
||||
console.dir(user.membership);
|
||||
const isCreating = !user.id || user.id === 0;
|
||||
console.log('Is creating: ', isCreating);
|
||||
const apiURL = `${BASE_API_URI}/auth/users`;
|
||||
|
||||
@@ -58,7 +66,7 @@ export const actions = {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `jwt=${cookies.get('jwt')}`
|
||||
},
|
||||
body: JSON.stringify(processedData)
|
||||
body: JSON.stringify(user)
|
||||
};
|
||||
|
||||
const res = await fetch(apiURL, requestOptions);
|
||||
@@ -128,18 +136,13 @@ export const actions = {
|
||||
*/
|
||||
updateCar: async ({ request, fetch, cookies }) => {
|
||||
let formData = await request.formData();
|
||||
console.dir(formData);
|
||||
const rawCar = /**@type {Partial<App.Types['car']>} */ (formDataToObject(formData).object);
|
||||
const car = processCarFormData(rawCar);
|
||||
|
||||
const rawFormData = formDataToObject(formData);
|
||||
/** @type {{object: Partial<App.Types['car']>, confirm_password: string}} */
|
||||
const rawData = {
|
||||
object: /** @type {Partial<App.Types['car']>} */ (rawFormData.object),
|
||||
confirm_password: rawFormData.confirm_password
|
||||
};
|
||||
const processedData = processCarFormData(rawData);
|
||||
|
||||
const isCreating = !processedData.car.id || processedData.car.id === 0;
|
||||
const isCreating = !car.id || car.id === 0;
|
||||
console.log('Is creating: ', isCreating);
|
||||
console.log('sending: ', JSON.stringify(processedData.car));
|
||||
console.log('sending: ', JSON.stringify(car.damages));
|
||||
|
||||
const apiURL = `${BASE_API_URI}/auth/cars`;
|
||||
|
||||
@@ -151,7 +154,7 @@ export const actions = {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `jwt=${cookies.get('jwt')}`
|
||||
},
|
||||
body: JSON.stringify(processedData.car)
|
||||
body: JSON.stringify(car)
|
||||
};
|
||||
|
||||
const res = await fetch(apiURL, requestOptions);
|
||||
@@ -164,6 +167,8 @@ export const actions = {
|
||||
|
||||
const response = await res.json();
|
||||
console.log('Server success response:', response);
|
||||
console.log('Server opponent response:', response.damages[0]?.opponent);
|
||||
console.log('Server insurance response:', response.damages[0]?.insurance);
|
||||
throw redirect(303, `${base}/auth/admin/users`);
|
||||
},
|
||||
|
||||
@@ -179,12 +184,8 @@ export const actions = {
|
||||
let formData = await request.formData();
|
||||
|
||||
const rawFormData = formDataToObject(formData);
|
||||
/** @type {{object: Partial<App.Locals['user']>, confirm_password: string}} */
|
||||
const rawData = {
|
||||
object: /** @type {Partial<App.Locals['user']>} */ (rawFormData.object),
|
||||
confirm_password: rawFormData.confirm_password
|
||||
};
|
||||
const processedData = processUserFormData(rawData);
|
||||
/** @type {Partial<App.Locals['user']>} */
|
||||
const rawUser = /** @type {Partial<App.Locals['user']>} */ (rawFormData.object);
|
||||
|
||||
const apiURL = `${BASE_API_URI}/auth/users`;
|
||||
|
||||
@@ -196,7 +197,7 @@ export const actions = {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `jwt=${cookies.get('jwt')}`
|
||||
},
|
||||
body: JSON.stringify(processedData)
|
||||
body: JSON.stringify({ id: Number(rawUser.id) })
|
||||
};
|
||||
|
||||
const res = await fetch(apiURL, requestOptions);
|
||||
@@ -217,14 +218,15 @@ export const actions = {
|
||||
* @param request - The request object
|
||||
* @param fetch - Fetch object from sveltekit
|
||||
* @param cookies - SvelteKit's cookie object
|
||||
* @param locals - The local object, housing current subscription
|
||||
* @returns
|
||||
*/
|
||||
subscriptionDelete: async ({ request, fetch, cookies }) => {
|
||||
let formData = await request.formData();
|
||||
|
||||
const rawData = formDataToObject(formData);
|
||||
const processedData = processSubscriptionFormData(rawData);
|
||||
|
||||
/** @type {Partial<App.Types['subscription']>} */
|
||||
const subscription = rawData.object;
|
||||
|
||||
const apiURL = `${BASE_API_URI}/auth/subscriptions`;
|
||||
|
||||
@@ -236,7 +238,45 @@ export const actions = {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `jwt=${cookies.get('jwt')}`
|
||||
},
|
||||
body: JSON.stringify(processedData.subscription)
|
||||
body: JSON.stringify({ id: Number(subscription.id), name: subscription.name })
|
||||
};
|
||||
|
||||
const res = await fetch(apiURL, requestOptions);
|
||||
|
||||
if (!res.ok) {
|
||||
const response = await res.json();
|
||||
const errors = formatError(response.errors);
|
||||
return fail(400, { errors: errors });
|
||||
}
|
||||
|
||||
const response = await res.json();
|
||||
console.log('Server success response:', response);
|
||||
throw redirect(303, `${base}/auth/admin/users`);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request - The request object
|
||||
* @param fetch - Fetch object from sveltekit
|
||||
* @param cookies - SvelteKit's cookie object
|
||||
* @returns
|
||||
*/
|
||||
carDelete: async ({ request, fetch, cookies }) => {
|
||||
let formData = await request.formData();
|
||||
console.dir(formData);
|
||||
const rawCar = formDataToObject(formData);
|
||||
|
||||
const apiURL = `${BASE_API_URI}/auth/cars`;
|
||||
console.log('sending delete request to', JSON.stringify({ id: Number(rawCar.object.id) }));
|
||||
/** @type {RequestInit} */
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `jwt=${cookies.get('jwt')}`
|
||||
},
|
||||
body: JSON.stringify({ id: Number(rawCar.object.id) })
|
||||
};
|
||||
|
||||
const res = await fetch(apiURL, requestOptions);
|
||||
@@ -263,12 +303,9 @@ export const actions = {
|
||||
let formData = await request.formData();
|
||||
|
||||
const rawFormData = formDataToObject(formData);
|
||||
/** @type {{object: Partial<App.Types['subscription']>, confirm_password: string}} */
|
||||
const rawData = {
|
||||
object: /** @type {Partial<App.Types['subscription']>} */ (rawFormData.object),
|
||||
confirm_password: rawFormData.confirm_password
|
||||
};
|
||||
const processedData = processUserFormData(rawData);
|
||||
/** @type {App.Locals['user']} */
|
||||
const rawUser = /** @type {App.Locals['user']} */ (rawFormData.object);
|
||||
const processedData = processUserFormData(rawUser);
|
||||
console.dir(processedData);
|
||||
const apiURL = `${BASE_API_URI}/auth/users/activate`;
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
on:click={() => setActiveSection('subscriptions')}
|
||||
>
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
{$t('subscription.subscriptions')}
|
||||
{$t('subscriptions.subscriptions')}
|
||||
<span class="nav-badge">{subscriptions.length}</span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -391,47 +391,51 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="button-group">
|
||||
<button
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
selected = user;
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
{$t('edit')}
|
||||
</button>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/userDelete"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success' || result.type === 'redirect') {
|
||||
await applyAction(result);
|
||||
}
|
||||
};
|
||||
}}
|
||||
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||
if (
|
||||
!confirm(
|
||||
$t('dialog.user_deletion', {
|
||||
values: {
|
||||
firstname: user.first_name || '',
|
||||
lastname: user.last_name || ''
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
e.preventDefault(); // Cancel form submission if user declines
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="user[id]" value={user.id} />
|
||||
<input type="hidden" name="user[last_name]" value={user.last_name} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
{#if hasPrivilige(user, PERMISSIONS.Update)}
|
||||
<button
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
selected = user;
|
||||
}}
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
{$t('edit')}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
{#if hasPrivilige(user, PERMISSIONS.Delete)}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/userDelete"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success' || result.type === 'redirect') {
|
||||
await applyAction(result);
|
||||
}
|
||||
};
|
||||
}}
|
||||
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||
if (
|
||||
!confirm(
|
||||
$t('dialog.user_deletion', {
|
||||
values: {
|
||||
firstname: user.first_name || '',
|
||||
lastname: user.last_name || ''
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
e.preventDefault(); // Cancel form submission if user declines
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="user[id]" value={user.id} />
|
||||
<input type="hidden" name="user[last_name]" value={user.last_name} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
@@ -439,7 +443,7 @@
|
||||
</div>
|
||||
{:else if activeSection === 'subscriptions'}
|
||||
<div class="section-header">
|
||||
<h2>{$t('subscription.subscriptions')}</h2>
|
||||
<h2>{$t('subscriptions.subscriptions')}</h2>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||
<button
|
||||
class="btn primary"
|
||||
@@ -468,7 +472,7 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{$t('subscription.monthly_fee')}</th>
|
||||
<th>{$t('subscriptions.monthly_fee')}</th>
|
||||
<td
|
||||
>{subscription.monthly_fee !== -1
|
||||
? subscription.monthly_fee + '€'
|
||||
@@ -476,7 +480,7 @@
|
||||
>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('subscription.hourly_rate')}</th>
|
||||
<th>{$t('subscriptions.hourly_rate')}</th>
|
||||
<td
|
||||
>{subscription.hourly_rate !== -1
|
||||
? subscription.hourly_rate + '€'
|
||||
@@ -484,11 +488,11 @@
|
||||
>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('subscription.included_hours_per_year')}</th>
|
||||
<th>{$t('subscriptions.included_hours_per_year')}</th>
|
||||
<td>{subscription.included_hours_per_year || 0}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('subscription.included_hours_per_month')}</th>
|
||||
<th>{$t('subscriptions.included_hours_per_month')}</th>
|
||||
<td>{subscription.included_hours_per_month || 0}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -496,13 +500,13 @@
|
||||
<td>{subscription.details || '-'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('subscription.conditions')}</th>
|
||||
<th>{$t('subscriptions.conditions')}</th>
|
||||
<td>{subscription.conditions || '-'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||
<div class="button-group">
|
||||
<div class="button-group">
|
||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||
<button
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
@@ -527,18 +531,31 @@
|
||||
?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
await applyAction(result);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="subscription[id]" value={subscription.id} />
|
||||
<input type="hidden" name="subscription[name]" value={subscription.name} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
};
|
||||
}}
|
||||
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||
if (
|
||||
!confirm(
|
||||
$t('dialog.subscription_deletion', {
|
||||
values: {
|
||||
name: subscription.name || ''
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
e.preventDefault(); // Cancel form submission if user declines
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="subscription[id]" value={subscription.id} />
|
||||
<input type="hidden" name="subscription[name]" value={subscription.name} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{/each}
|
||||
@@ -597,8 +614,8 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{#if hasPrivilige(user, PERMISSIONS.Update)}
|
||||
<div class="button-group">
|
||||
<div class="button-group">
|
||||
{#if hasPrivilige(user, PERMISSIONS.Update)}
|
||||
<button
|
||||
class="btn primary"
|
||||
on:click={() => {
|
||||
@@ -608,6 +625,8 @@
|
||||
<i class="fas fa-edit"></i>
|
||||
{$t('edit')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if hasPrivilige(user, PERMISSIONS.Delete)}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/carDelete"
|
||||
@@ -637,14 +656,14 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="subscription[id]" value={car.id} />
|
||||
<input type="hidden" name="car[id]" value={car.id} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{/each}
|
||||
@@ -705,7 +724,7 @@
|
||||
</Modal>
|
||||
{:else if selected && 'brand' in selected}
|
||||
<Modal on:close={close}>
|
||||
<CarEditForm {form} editor={user} car={selected} on:cancel={close} on:close={close} />
|
||||
<CarEditForm {form} editor={user} {users} car={selected} on:cancel={close} on:close={close} />
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -62,6 +62,16 @@ type SecurityConfig struct {
|
||||
Burst int `json:"Burst" default:"60" envconfig:"BURST_LIMIT"`
|
||||
} `json:"RateLimits"`
|
||||
}
|
||||
|
||||
type CompanyConfig struct {
|
||||
Name string `json:"Name" envconfig:"COMPANY_NAME"`
|
||||
Address string `json:"Address" envconfig:"COMPANY_ADDRESS"`
|
||||
City string `json:"City" envconfig:"COMPANY_CITY"`
|
||||
ZipCode string `json:"ZipCode" envconfig:"COMPANY_ZIPCODE"`
|
||||
Country string `json:"Country" envconfig:"COMPANY_COUNTRY"`
|
||||
SepaPrefix string `json:"SepaPrefix" envconfig:"COMPANY_SEPA_PREFIX"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Auth AuthenticationConfig `json:"auth"`
|
||||
Site SiteConfig `json:"site"`
|
||||
@@ -72,6 +82,7 @@ type Config struct {
|
||||
DB DatabaseConfig `json:"db"`
|
||||
SMTP SMTPConfig `json:"smtp"`
|
||||
Security SecurityConfig `json:"security"`
|
||||
Company CompanyConfig `json:"company"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -85,7 +96,9 @@ var (
|
||||
Recipients RecipientsConfig
|
||||
Env string
|
||||
Security SecurityConfig
|
||||
Company CompanyConfig
|
||||
)
|
||||
|
||||
var environmentOptions map[string]bool = map[string]bool{
|
||||
"development": true,
|
||||
"production": true,
|
||||
@@ -124,6 +137,7 @@ func LoadConfig() {
|
||||
Security = CFG.Security
|
||||
Env = CFG.Env
|
||||
Site = CFG.Site
|
||||
Company = CFG.Company
|
||||
logger.Info.Printf("Config loaded: %#v", CFG)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ const (
|
||||
DelayedPaymentStatus
|
||||
SettledPaymentStatus
|
||||
AwaitingPaymentStatus
|
||||
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
||||
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
|
||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||
SupporterSubscriptionModelName = "Keins"
|
||||
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
||||
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
|
||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||
SupporterSubscriptionName = "Keins"
|
||||
)
|
||||
|
||||
var Licences = struct {
|
||||
|
||||
@@ -89,9 +89,7 @@ func (cr *CarController) GetAll(c *gin.Context) {
|
||||
|
||||
func (cr *CarController) Delete(c *gin.Context) {
|
||||
type input struct {
|
||||
Car struct {
|
||||
ID uint `json:"id" binding:"required,numeric"`
|
||||
} `json:"car"`
|
||||
ID uint `json:"id" binding:"required,numeric"`
|
||||
}
|
||||
var deleteData input
|
||||
requestUser, err := cr.UserService.FromContext(c)
|
||||
@@ -109,7 +107,7 @@ func (cr *CarController) Delete(c *gin.Context) {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
err = cr.S.Delete(&deleteData.Car.ID)
|
||||
err = cr.S.Delete(&deleteData.ID)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error deleting car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestMain(t *testing.T) {
|
||||
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
||||
|
||||
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
|
||||
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
|
||||
var subscriptionRepo repositories.SubscriptionsRepositoryInterface = &repositories.SubscriptionsRepository{}
|
||||
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
|
||||
|
||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||
@@ -119,6 +119,7 @@ func TestMain(t *testing.T) {
|
||||
if err := initLicenceCategories(); err != nil {
|
||||
log.Fatalf("Failed to init Categories: %v", err)
|
||||
}
|
||||
password := "securepassword"
|
||||
admin := models.User{
|
||||
FirstName: "Ad",
|
||||
LastName: "min",
|
||||
@@ -130,7 +131,7 @@ func TestMain(t *testing.T) {
|
||||
ZipCode: "12345",
|
||||
City: "SampleCity",
|
||||
Status: constants.ActiveStatus,
|
||||
Password: "",
|
||||
Password: password,
|
||||
Notes: "",
|
||||
RoleID: constants.Roles.Admin,
|
||||
Consents: nil,
|
||||
@@ -140,7 +141,6 @@ func TestMain(t *testing.T) {
|
||||
Licence: &models.Licence{
|
||||
Status: constants.UnverifiedStatus,
|
||||
}}
|
||||
admin.SetPassword("securepassword")
|
||||
admin.Create(db)
|
||||
validation.SetupValidators(db)
|
||||
t.Run("userController", func(t *testing.T) {
|
||||
@@ -203,7 +203,7 @@ func initLicenceCategories() error {
|
||||
}
|
||||
|
||||
func initSubscriptionPlans() error {
|
||||
subscriptions := []models.SubscriptionModel{
|
||||
subscriptions := []models.Subscription{
|
||||
{
|
||||
Name: "Basic",
|
||||
Details: "Test Plan",
|
||||
@@ -284,7 +284,7 @@ func getBaseUser() models.User {
|
||||
City: "Hasloh",
|
||||
Phone: "01738484993",
|
||||
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||
Membership: &models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
|
||||
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
|
||||
Licence: nil,
|
||||
Password: "passw@#$#%$!-ord123",
|
||||
Company: "",
|
||||
@@ -303,7 +303,7 @@ func getBaseSupporter() models.User {
|
||||
City: "Hasloh",
|
||||
Phone: "01738484993",
|
||||
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||
Membership: &models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
|
||||
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
|
||||
Licence: nil,
|
||||
Password: "passw@#$#%$!-ord123",
|
||||
Company: "",
|
||||
|
||||
@@ -33,7 +33,7 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var subscription models.SubscriptionModel
|
||||
var subscription models.Subscription
|
||||
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
@@ -43,9 +43,9 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
||||
id, err := mc.Service.RegisterSubscription(&subscription)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||
utils.RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Duplicate)
|
||||
utils.RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.Subscription, errors.Responses.Keys.Duplicate)
|
||||
} else {
|
||||
utils.RespondWithError(c, err, "Couldn't register Membershipmodel", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
|
||||
utils.RespondWithError(c, err, "Couldn't register Membershipmodel", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func (mc *MembershipController) UpdateHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var subscription models.SubscriptionModel
|
||||
var subscription models.Subscription
|
||||
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
@@ -122,7 +122,7 @@ func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
|
||||
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
|
||||
subscriptions, err := mc.Service.GetSubscriptions(nil)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error retrieving subscriptions", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
|
||||
utils.RespondWithError(c, err, "Error retrieving subscriptions", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@ func (dt *DeleteSubscriptionTest) ValidateResult() error {
|
||||
return validateSubscription(dt.Assert, dt.WantDBData)
|
||||
}
|
||||
|
||||
func getBaseSubscription() models.SubscriptionModel {
|
||||
return models.SubscriptionModel{
|
||||
func getBaseSubscription() models.Subscription {
|
||||
return models.Subscription{
|
||||
Name: "Premium",
|
||||
Details: "A subscription detail",
|
||||
MonthlyFee: 12.0,
|
||||
@@ -157,7 +157,7 @@ func getBaseSubscription() models.SubscriptionModel {
|
||||
}
|
||||
}
|
||||
|
||||
func customizeSubscription(customize func(models.SubscriptionModel) models.SubscriptionModel) models.SubscriptionModel {
|
||||
func customizeSubscription(customize func(models.Subscription) models.Subscription) models.Subscription {
|
||||
subscription := getBaseSubscription()
|
||||
return customize(subscription)
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Details = ""
|
||||
return subscription
|
||||
})),
|
||||
@@ -187,7 +187,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": ""},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = ""
|
||||
return subscription
|
||||
})),
|
||||
@@ -200,7 +200,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantResponse: http.StatusBadRequest,
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(customizeSubscription(func(sub models.SubscriptionModel) models.SubscriptionModel {
|
||||
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
|
||||
sub.MonthlyFee = -10.0
|
||||
return sub
|
||||
})),
|
||||
@@ -213,7 +213,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantResponse: http.StatusBadRequest,
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(customizeSubscription(func(sub models.SubscriptionModel) models.SubscriptionModel {
|
||||
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
|
||||
sub.HourlyRate = -1.0
|
||||
return sub
|
||||
})),
|
||||
@@ -227,7 +227,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Conditions = "Some Condition"
|
||||
subscription.IncludedPerYear = 0
|
||||
subscription.IncludedPerMonth = 1
|
||||
@@ -243,7 +243,7 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Conditions = "Some Condition"
|
||||
subscription.IncludedPerYear = 0
|
||||
subscription.IncludedPerMonth = 1
|
||||
@@ -274,7 +274,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.MonthlyFee = 123.0
|
||||
return subscription
|
||||
})),
|
||||
@@ -288,7 +288,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.ID = 0
|
||||
return subscription
|
||||
})),
|
||||
@@ -302,7 +302,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.HourlyRate = 3254.0
|
||||
return subscription
|
||||
})),
|
||||
@@ -316,7 +316,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.IncludedPerYear = 9873.0
|
||||
return subscription
|
||||
})),
|
||||
@@ -330,7 +330,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.IncludedPerMonth = 23415.0
|
||||
return subscription
|
||||
})),
|
||||
@@ -344,7 +344,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = "NonExistentSubscription"
|
||||
return subscription
|
||||
})),
|
||||
@@ -358,7 +358,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Details = "Altered Details"
|
||||
subscription.Conditions = "Some Condition"
|
||||
subscription.IncludedPerYear = 0
|
||||
@@ -375,7 +375,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Details = "Altered Details"
|
||||
subscription.Conditions = "Some Condition"
|
||||
subscription.IncludedPerYear = 0
|
||||
@@ -388,7 +388,7 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||
|
||||
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
|
||||
var premiumSub, basicSub models.SubscriptionModel
|
||||
var premiumSub, basicSub models.Subscription
|
||||
database.DB.Where("name = ?", "Premium").First(&premiumSub)
|
||||
database.DB.Where("name = ?", "Basic").First(&basicSub)
|
||||
|
||||
@@ -402,7 +402,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = "NonExistentSubscription"
|
||||
subscription.ID = basicSub.ID
|
||||
logger.Error.Printf("subscription to delete: %#v", subscription)
|
||||
@@ -418,7 +418,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": ""},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = ""
|
||||
subscription.ID = basicSub.ID
|
||||
return subscription
|
||||
@@ -433,7 +433,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Basic"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = "Basic"
|
||||
subscription.ID = basicSub.ID
|
||||
return subscription
|
||||
@@ -448,7 +448,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: true,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = "Premium"
|
||||
subscription.ID = premiumSub.ID
|
||||
return subscription
|
||||
@@ -463,7 +463,7 @@ func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(
|
||||
customizeSubscription(func(subscription models.SubscriptionModel) models.SubscriptionModel {
|
||||
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||
subscription.Name = "Premium"
|
||||
subscription.ID = premiumSub.ID
|
||||
return subscription
|
||||
|
||||
@@ -132,9 +132,9 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !user.Verify(input.Token, constants.VerificationTypes.Password) {
|
||||
utils.RespondWithError(c, errors.ErrAlreadyVerified, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||
err = user.Verify(input.Token, constants.VerificationTypes.Password)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ func setupTestContext() (*TestContext, error) {
|
||||
logger.Error.Printf("error fetching user: %#v", err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Error.Printf("found user: %#v", user)
|
||||
return &TestContext{
|
||||
router: gin.Default(),
|
||||
response: httptest.NewRecorder(),
|
||||
@@ -104,7 +103,6 @@ func testChangePassword(t *testing.T, tc *TestContext) {
|
||||
var verification models.Verification
|
||||
result := database.DB.Where("user_id = ? AND type = ?", tc.user.ID, constants.VerificationTypes.Password).First(&verification)
|
||||
assert.NoError(t, result.Error)
|
||||
logger.Error.Printf("token from db: %#v", verification.VerificationToken)
|
||||
requestBody := map[string]interface{}{
|
||||
"password": "new-pas9247A@!sword",
|
||||
"token": verification.VerificationToken,
|
||||
|
||||
@@ -89,6 +89,10 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
||||
|
||||
var updateData RegistrationData
|
||||
if err := c.ShouldBindJSON(&updateData); err != nil {
|
||||
if updateData.User.Password != "" {
|
||||
|
||||
logger.Error.Printf("u.password: %#v", updateData.User.Password)
|
||||
}
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
@@ -240,12 +244,12 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
logger.Info.Printf("Registering user %v", regData.User.Email)
|
||||
selectedModel, err := uc.MembershipService.GetSubscriptionByName(®Data.User.Membership.SubscriptionModel.Name)
|
||||
selectedModel, err := uc.MembershipService.GetSubscriptionByName(®Data.User.Membership.Subscription.Name)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InvalidSubscriptionModel)
|
||||
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.InvalidSubscription)
|
||||
return
|
||||
}
|
||||
regData.User.Membership.SubscriptionModel = *selectedModel
|
||||
regData.User.Membership.Subscription = *selectedModel
|
||||
// Get Gin's binding validator engine with all registered validators
|
||||
validate := binding.Validator.Engine().(*validator.Validate)
|
||||
|
||||
@@ -254,7 +258,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
if regData.User.Membership.SubscriptionModel.Name == constants.SupporterSubscriptionModelName {
|
||||
if regData.User.Membership.Subscription.Name == constants.SupporterSubscriptionName {
|
||||
regData.User.RoleID = constants.Roles.Supporter
|
||||
} else {
|
||||
regData.User.RoleID = constants.Roles.Member
|
||||
@@ -347,7 +351,8 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) {
|
||||
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user"})
|
||||
return
|
||||
}
|
||||
if !user.Verify(token, constants.VerificationTypes.Email) {
|
||||
err = user.Verify(token, constants.VerificationTypes.Email)
|
||||
if err != nil {
|
||||
logger.Error.Printf("Couldn't find user verification in verifyMailHandler: %v", err)
|
||||
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user verification request"})
|
||||
return
|
||||
|
||||
@@ -351,8 +351,8 @@ func testCurrentUserHandler(t *testing.T, loginEmail string) http.Cookie {
|
||||
|
||||
if tt.expectedStatus == http.StatusOK {
|
||||
var response struct {
|
||||
User models.User `json:"user"`
|
||||
Subscriptions []models.SubscriptionModel `json:"subscriptions"`
|
||||
User models.User `json:"user"`
|
||||
Subscriptions []models.Subscription `json:"subscriptions"`
|
||||
}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
@@ -407,12 +407,15 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
||||
if assert {
|
||||
user := (*users)[0]
|
||||
// Check for mandate reference
|
||||
if user.BankAccount.MandateReference == "" {
|
||||
|
||||
if user.BankAccount.IBAN != "" && user.BankAccount.MandateReference == "" {
|
||||
return fmt.Errorf("Mandate reference not generated for user: %s", user.Email)
|
||||
} else if user.BankAccount.IBAN == "" && user.BankAccount.MandateReference != "" {
|
||||
return fmt.Errorf("Mandate reference generated without IBAN for user: %s", user.Email)
|
||||
}
|
||||
|
||||
// Validate mandate reference format
|
||||
expected := user.GenerateMandateReference()
|
||||
expected := user.BankAccount.GenerateMandateReference(user.ID)
|
||||
if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
|
||||
return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
|
||||
}
|
||||
@@ -686,6 +689,20 @@ func testUpdateUser(t *testing.T) {
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "Admin Password Update low entropy should fail",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(AdminCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = "newpassword"
|
||||
|
||||
},
|
||||
expectedErrors: []map[string]string{
|
||||
{"field": "server.validation.special server.validation.uppercase server.validation.numbers server.validation.longer", "key": "server.validation.insecure"},
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "Admin Password Update",
|
||||
setupCookie: func(req *http.Request) {
|
||||
@@ -792,7 +809,11 @@ func testUpdateUser(t *testing.T) {
|
||||
if updatedUser.Password == "" {
|
||||
assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
|
||||
} else {
|
||||
assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password)
|
||||
matches, err := updatedUserFromDB.PasswordMatches(updatedUser.Password)
|
||||
if err != nil {
|
||||
t.Fatalf("Error matching password: %v", err)
|
||||
}
|
||||
assert.True(t, matches, "Password mismatch")
|
||||
}
|
||||
|
||||
updatedUserFromDB.Password = ""
|
||||
@@ -820,7 +841,7 @@ func testUpdateUser(t *testing.T) {
|
||||
assert.Equal(t, updatedUser.Membership.StartDate, updatedUserFromDB.Membership.StartDate, "Membership.StartDate mismatch")
|
||||
assert.Equal(t, updatedUser.Membership.EndDate, updatedUserFromDB.Membership.EndDate, "Membership.EndDate mismatch")
|
||||
assert.Equal(t, updatedUser.Membership.Status, updatedUserFromDB.Membership.Status, "Membership.Status mismatch")
|
||||
assert.Equal(t, updatedUser.Membership.SubscriptionModelID, updatedUserFromDB.Membership.SubscriptionModelID, "Membership.SubscriptionModelID mismatch")
|
||||
assert.Equal(t, updatedUser.Membership.SubscriptionID, updatedUserFromDB.Membership.SubscriptionID, "Membership.SubscriptionID mismatch")
|
||||
assert.Equal(t, updatedUser.Membership.ParentMembershipID, updatedUserFromDB.Membership.ParentMembershipID, "Membership.ParentMembershipID mismatch")
|
||||
|
||||
if updatedUser.Licence == nil {
|
||||
@@ -871,11 +892,11 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error {
|
||||
if !strings.Contains(message.Body, user.FirstName) {
|
||||
return fmt.Errorf("User first name(%v) has not been rendered in registration mail.", user.FirstName)
|
||||
}
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
|
||||
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.Subscription.MonthlyFee)) {
|
||||
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.MonthlyFee)
|
||||
}
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
|
||||
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.Subscription.HourlyRate)) {
|
||||
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.HourlyRate)
|
||||
}
|
||||
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
|
||||
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
||||
@@ -907,11 +928,11 @@ func checkRegistrationMail(message *utils.Email, user *models.User) error {
|
||||
if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
|
||||
return fmt.Errorf("User first and last name(%v) has not been rendered in registration mail.", user.FirstName+" "+user.LastName)
|
||||
}
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.SubscriptionModel.MonthlyFee)) {
|
||||
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.MonthlyFee)
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/Monat</strong>: %v", user.Membership.Subscription.MonthlyFee)) {
|
||||
return fmt.Errorf("Users monthly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.MonthlyFee)
|
||||
}
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.SubscriptionModel.HourlyRate)) {
|
||||
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.SubscriptionModel.HourlyRate)
|
||||
if !strings.Contains(message.Body, fmt.Sprintf("Preis/h</strong>: %v", user.Membership.Subscription.HourlyRate)) {
|
||||
return fmt.Errorf("Users hourly subscription fee(%v) has not been rendered in registration mail.", user.Membership.Subscription.HourlyRate)
|
||||
}
|
||||
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
|
||||
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
||||
@@ -951,7 +972,7 @@ func checkVerificationMail(message *utils.Email, user *models.User) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
|
||||
}
|
||||
v, err := user.GetVerification(constants.VerificationTypes.Email)
|
||||
v, err := user.FindVerification(constants.VerificationTypes.Email)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting verification token: %v", err.Error())
|
||||
}
|
||||
@@ -1132,7 +1153,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
user.BankAccount.IBAN = "DE1234234123134"
|
||||
user.RoleID = constants.Roles.Supporter
|
||||
user.Email = "john.supporter@example.com"
|
||||
user.Membership.SubscriptionModel.Name = constants.SupporterSubscriptionModelName
|
||||
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1145,7 +1166,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
user.BankAccount.IBAN = ""
|
||||
user.RoleID = constants.Roles.Supporter
|
||||
user.Email = "john.supporter@example.com"
|
||||
user.Membership.SubscriptionModel.Name = constants.SupporterSubscriptionModelName
|
||||
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1155,7 +1176,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||
user.Membership.SubscriptionModel.Name = ""
|
||||
user.Membership.Subscription.Name = ""
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1165,7 +1186,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||
user.Membership.SubscriptionModel.Name = "NOTEXISTENTPLAN"
|
||||
user.Membership.Subscription.Name = "NOTEXISTENTPLAN"
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1204,7 +1225,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
Assert: false,
|
||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||
user.Email = "john.junior.doe@example.com"
|
||||
user.Membership.SubscriptionModel.Name = "additional"
|
||||
user.Membership.Subscription.Name = "additional"
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1216,7 +1237,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||
user.Email = "john.junior.doe@example.com"
|
||||
user.Membership.ParentMembershipID = 200
|
||||
user.Membership.SubscriptionModel.Name = "additional"
|
||||
user.Membership.Subscription.Name = "additional"
|
||||
return user
|
||||
})),
|
||||
},
|
||||
@@ -1228,7 +1249,7 @@ func getTestUsers() []RegisterUserTest {
|
||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||
user.Email = "john.junior.doe@example.com"
|
||||
user.Membership.ParentMembershipID = 1
|
||||
user.Membership.SubscriptionModel.Name = "additional"
|
||||
user.Membership.Subscription.Name = "additional"
|
||||
return user
|
||||
})),
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -53,7 +52,7 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
|
||||
|
||||
if err := db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.SubscriptionModel{},
|
||||
&models.Subscription{},
|
||||
&models.Membership{},
|
||||
&models.Consent{},
|
||||
&models.Verification{},
|
||||
@@ -83,12 +82,12 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
|
||||
}
|
||||
|
||||
var subscriptionsCount int64
|
||||
db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount)
|
||||
subscriptionModels := createSubscriptionModels()
|
||||
for _, model := range subscriptionModels {
|
||||
db.Model(&models.Subscription{}).Count(&subscriptionsCount)
|
||||
subscriptions := createSubscriptions()
|
||||
for _, model := range subscriptions {
|
||||
var exists int64
|
||||
db.
|
||||
Model(&models.SubscriptionModel{}).
|
||||
Model(&models.Subscription{}).
|
||||
Where("name = ?", model.Name).
|
||||
Count(&exists)
|
||||
logger.Error.Printf("looked for model.name %v and found %v", model.Name, exists)
|
||||
@@ -103,7 +102,7 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
|
||||
var userCount int64
|
||||
db.Model(&models.User{}).Count(&userCount)
|
||||
if userCount == 0 {
|
||||
var createdModel models.SubscriptionModel
|
||||
var createdModel models.Subscription
|
||||
if err := db.First(&createdModel).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,10 +117,10 @@ func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func createSubscriptionModels() []models.SubscriptionModel {
|
||||
return []models.SubscriptionModel{
|
||||
func createSubscriptions() []models.Subscription {
|
||||
return []models.Subscription{
|
||||
{
|
||||
Name: constants.SupporterSubscriptionModelName,
|
||||
Name: constants.SupporterSubscriptionName,
|
||||
Details: "Dieses Modell ist für Sponsoren und Nichtmitglieder, die keinen Vereinsmitglied sind.",
|
||||
HourlyRate: 999,
|
||||
MonthlyFee: 0,
|
||||
@@ -162,11 +161,6 @@ func createAdmin(userMail string) (*models.User, error) {
|
||||
// Encode into a URL-safe base64 string
|
||||
password := base64.URLEncoding.EncodeToString(passwordBytes)[:12]
|
||||
|
||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Error.Print("==============================================================")
|
||||
logger.Error.Printf("Admin Email: %v", userMail)
|
||||
logger.Error.Printf("Admin Password: %v", password)
|
||||
@@ -176,7 +170,7 @@ func createAdmin(userMail string) (*models.User, error) {
|
||||
FirstName: "Ad",
|
||||
LastName: "Min",
|
||||
DateOfBirth: time.Now().AddDate(-20, 0, 0),
|
||||
Password: hash,
|
||||
Password: password,
|
||||
Company: "",
|
||||
Address: "",
|
||||
ZipCode: "",
|
||||
|
||||
@@ -1,13 +1,54 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Insurance struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
OwnerID uint `gorm:"not null" json:"owner_id" binding:"numeric"`
|
||||
Cars []Car `gorm:"many2many:car_insurances;" json:"-"`
|
||||
Company string `json:"company" binding:"safe_content"`
|
||||
Reference string `json:"reference" binding:"safe_content"`
|
||||
Notes string `json:"notes" binding:"safe_content"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
}
|
||||
|
||||
func (i *Insurance) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(i).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info.Printf("Insurance created: %#v", i)
|
||||
// Preload all associations to return the fully populated User
|
||||
return tx.
|
||||
First(i, i.ID).Error // Refresh the user object with all associations
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Insurance) Update(db *gorm.DB) error {
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingInsurance Insurance
|
||||
|
||||
logger.Info.Printf("updating Insurance: %#v", i)
|
||||
if err := tx.First(&existingInsurance, i.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&existingInsurance).Updates(i).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.First(i, i.ID).Error
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (i *Insurance) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&i).Error
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/internal/config"
|
||||
"GoMembership/pkg/logger"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -48,3 +50,10 @@ func (b *BankAccount) Update(db *gorm.DB) error {
|
||||
func (b *BankAccount) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&b).Error
|
||||
}
|
||||
|
||||
func (b *BankAccount) GenerateMandateReference(id uint) string {
|
||||
if b.IBAN == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s-%s%d-%s", config.Company.SepaPrefix, time.Now().Format("20060102"), id, b.IBAN[len(b.IBAN)-4:])
|
||||
}
|
||||
|
||||
@@ -1,123 +1,151 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type Car struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Status uint `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Brand string `gorm:"not null" json:"brand"`
|
||||
Model string `gorm:"not null" json:"model"`
|
||||
Color string `gorm:"not null" json:"color"`
|
||||
LicencePlate string `gorm:"not null,unique" json:"licence_plate"`
|
||||
Price float32 `json:"price"`
|
||||
Rate float32 `json:"rate"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Location Location `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"location"`
|
||||
LocationID uint
|
||||
Damages *[]Damage `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"damages"`
|
||||
Insurances *[]Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Latitude float32 `json:"latitude"`
|
||||
Longitude float32 `json:"longitude"`
|
||||
}
|
||||
|
||||
type Damage struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CarID uint `json:"car_id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Opponent *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"opponent"`
|
||||
OpponentID uint
|
||||
Insurance *Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
|
||||
InsuranceID uint
|
||||
Notes string `json:"notes"`
|
||||
Status uint `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Brand string `gorm:"not null" json:"brand"`
|
||||
Model string `gorm:"not null" json:"model"`
|
||||
Color string `gorm:"not null" json:"color"`
|
||||
LicencePlate string `gorm:"not null,unique" json:"licence_plate"`
|
||||
Price float32 `gorm:"type:decimal(10,2)" json:"price"`
|
||||
Rate float32 `gorm:"type:decimal(10,2)" json:"rate"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Location *Location `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"location"`
|
||||
Damages []Damage `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"damages"`
|
||||
Insurances []Insurance `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;many2many:car_insurances" json:"insurances"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
func (c *Car) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(c).Error; err != nil {
|
||||
if err := tx.Preload(clause.Associations).Create(c).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace associated Categories (assumes Categories already exist)
|
||||
if c.Insurances != nil {
|
||||
if err := tx.Model(c).Association("Insurances").Replace(c.Insurances); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.Info.Printf("car created: %#v", c)
|
||||
// Preload all associations to return the fully populated User
|
||||
return tx.
|
||||
Preload("Insurances").
|
||||
First(c, c.ID).Error // Refresh the user object with all associations
|
||||
Preload(clause.Associations).
|
||||
First(c, c.ID).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Car) Update(db *gorm.DB) error {
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingCar Car
|
||||
|
||||
logger.Info.Printf("updating car: %#v", c)
|
||||
if err := tx.
|
||||
Preload("Insurances").
|
||||
if err := tx.Preload("Damages.Insurance").
|
||||
Preload("Damages.Opponent").
|
||||
First(&existingCar, c.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c)
|
||||
if result.Error != nil {
|
||||
logger.Error.Printf("car update error: %#v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.ErrNoRowsAffected
|
||||
}
|
||||
|
||||
if c.Insurances != nil {
|
||||
if err := tx.Save(*c.Insurances).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// if err := tx.Model(c).Association("Damages").Replace(c.Damages); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if err := tx.Model(c).Association("Insurances").Replace(c.Insurances); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// Calculate damage IDs to delete
|
||||
// existingDamageIDs := make(map[uint]bool)
|
||||
// for _, d := range existingCar.Damages {
|
||||
// existingDamageIDs[d.ID] = true
|
||||
// }
|
||||
|
||||
// newDamageIDs := make(map[uint]bool)
|
||||
// for _, d := range c.Damages {
|
||||
// if d.ID != 0 {
|
||||
// newDamageIDs[d.ID] = true
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Find IDs to delete
|
||||
// var toDelete []uint
|
||||
// for id := range existingDamageIDs {
|
||||
// if !newDamageIDs[id] {
|
||||
// toDelete = append(toDelete, id)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Batch delete orphaned damages
|
||||
// if len(toDelete) > 0 {
|
||||
// if err := tx.Where("id IN ?", toDelete).Delete(&Damage{}).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if len(c.Insurances) > 0 {
|
||||
// logger.Info.Printf("updating insurances: %#v", c.Insurances)
|
||||
// if err := tx.Model(&existingCar).Association("Insurances").Replace(c.Insurances); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Upsert new damages
|
||||
// for _, damage := range c.Damages {
|
||||
// // Process relationships
|
||||
// if damage.Opponent != nil {
|
||||
// if err := tx.Save(damage.Opponent).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// damage.OpponentID = damage.Opponent.ID
|
||||
// }
|
||||
|
||||
// if damage.Insurance != nil {
|
||||
// if err := tx.Save(damage.Insurance).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// damage.InsuranceID = damage.Insurance.ID
|
||||
// }
|
||||
|
||||
// // Create or update damage
|
||||
// if err := tx.Save(damage).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Update associations
|
||||
// if err := tx.Model(&existingCar).Association("Damages").Replace(c.Damages); err != nil {
|
||||
// return err
|
||||
// }
|
||||
return tx.
|
||||
Preload(clause.Associations).
|
||||
Preload("Damages").
|
||||
Preload("Insurances").
|
||||
First(c, c.ID).Error
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.
|
||||
Preload("Insurances").
|
||||
First(&c, c.ID).Error
|
||||
}
|
||||
|
||||
func (c *Car) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&c).Error
|
||||
return db.Select(clause.Associations).Delete(&c).Error
|
||||
}
|
||||
|
||||
func GetAllCars(db *gorm.DB) ([]Car, error) {
|
||||
var cars []Car
|
||||
if err := db.Find(&cars).Error; err != nil {
|
||||
if err := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Damages").
|
||||
Preload("Insurances").
|
||||
Find(&cars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cars, nil
|
||||
@@ -125,7 +153,11 @@ func GetAllCars(db *gorm.DB) ([]Car, error) {
|
||||
|
||||
func (c *Car) FromID(db *gorm.DB, id uint) error {
|
||||
var car Car
|
||||
if err := db.Preload("Insurances").First(&car, id).Error; err != nil {
|
||||
if err := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Damages").
|
||||
Preload("Insurances").
|
||||
First(&car, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
*c = car
|
||||
|
||||
58
go-backend/internal/models/damage.go
Normal file
58
go-backend/internal/models/damage.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Damage struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CarID uint `json:"car_id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Name string `json:"name"`
|
||||
Date time.Time `json:"date"`
|
||||
Opponent *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"opponent"`
|
||||
OpponentID uint `json:"opponent_id"`
|
||||
Driver *User `json:"driver"`
|
||||
DriverID uint `json:"driver_id"`
|
||||
Insurance *Insurance `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
|
||||
InsuranceID uint `json:"insurance_id"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
func (d *Damage) Create(db *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := db.Create(d).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info.Printf("Damage created: %#v", d)
|
||||
// Preload all associations to return the fully populated User
|
||||
return db.First(d, d.ID).Error // Refresh the user object with all associations
|
||||
}
|
||||
|
||||
func (d *Damage) Update(db *gorm.DB) error {
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingDamage Damage
|
||||
|
||||
logger.Info.Printf("updating Damage: %#v", d)
|
||||
if err := tx.First(&existingDamage, d.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&existingDamage).Updates(d).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.First(d, d.ID).Error
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (d *Damage) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&d).Error
|
||||
}
|
||||
54
go-backend/internal/models/location.go
Normal file
54
go-backend/internal/models/location.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
CarID uint `gorm:"index" json:"car_id"`
|
||||
Latitude float32 `json:"latitude"`
|
||||
Longitude float32 `json:"longitude"`
|
||||
}
|
||||
|
||||
func (l *Location) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(l).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info.Printf("Location created: %#v", l)
|
||||
// Preload all associations to return the fully populated User
|
||||
return tx.
|
||||
First(l, l.ID).Error // Refresh the user object with all associations
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Location) Update(db *gorm.DB) error {
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingLocation Location
|
||||
|
||||
logger.Info.Printf("updating Location: %#v", l)
|
||||
if err := tx.First(&existingLocation, l.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&existingLocation).Updates(l).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.First(l, l.ID).Error
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (l *Location) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&l).Error
|
||||
}
|
||||
@@ -8,20 +8,20 @@ import (
|
||||
)
|
||||
|
||||
type Membership struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"index" json:"user_id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Status int8 `json:"status" binding:"number,safe_content"`
|
||||
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription"`
|
||||
SubscriptionModelID uint `json:"subsription_model_id"`
|
||||
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"index" json:"user_id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Status int8 `json:"status" binding:"number,safe_content"`
|
||||
Subscription Subscription `gorm:"foreignKey:SubscriptionID" json:"subscription"`
|
||||
SubscriptionID uint `json:"subscription_id"`
|
||||
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
|
||||
}
|
||||
|
||||
func (m *Membership) BeforeSave(tx *gorm.DB) error {
|
||||
m.SubscriptionModelID = m.SubscriptionModel.ID
|
||||
m.SubscriptionID = m.Subscription.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func (m *Membership) Create(db *gorm.DB) error {
|
||||
}
|
||||
logger.Info.Printf("Membership created: %#v", m)
|
||||
|
||||
return db.Preload("SubscriptionModel").First(m, m.ID).Error // Refresh the user object with SubscriptionModel
|
||||
return db.Preload("Subscription").First(m, m.ID).Error // Refresh the user object with Subscription
|
||||
}
|
||||
|
||||
func (m *Membership) Update(db *gorm.DB) error {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SubscriptionModel struct {
|
||||
type Subscription struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
@@ -21,31 +21,31 @@ type SubscriptionModel struct {
|
||||
IncludedPerMonth int16 `json:"included_hours_per_month"`
|
||||
}
|
||||
|
||||
func (s *SubscriptionModel) Create(db *gorm.DB) error {
|
||||
func (s *Subscription) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(s).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info.Printf("SubscriptionModel created: %#v", s)
|
||||
logger.Info.Printf("Subscription created: %#v", s)
|
||||
// Preload all associations to retuvn the fully populated User
|
||||
return tx.
|
||||
First(s, s.ID).Error // Refresh the user object with all associations
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SubscriptionModel) Update(db *gorm.DB) error {
|
||||
func (s *Subscription) Update(db *gorm.DB) error {
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingSubscriptionModel SubscriptionModel
|
||||
var existingSubscription Subscription
|
||||
|
||||
logger.Info.Printf("updating SubscriptionModel: %#v", s)
|
||||
if err := tx.First(&existingSubscriptionModel, s.ID).Error; err != nil {
|
||||
logger.Info.Printf("updating Subscription: %#v", s)
|
||||
if err := tx.First(&existingSubscription, s.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&existingSubscriptionModel).Updates(s).Error; err != nil {
|
||||
if err := tx.Model(&existingSubscription).Updates(s).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.First(s, s.ID).Error
|
||||
@@ -53,6 +53,6 @@ func (s *SubscriptionModel) Update(db *gorm.DB) error {
|
||||
|
||||
}
|
||||
|
||||
func (s *SubscriptionModel) Delete(db *gorm.DB) error {
|
||||
func (s *Subscription) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&s).Error
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package models
|
||||
import (
|
||||
"GoMembership/internal/config"
|
||||
"GoMembership/internal/constants"
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"fmt"
|
||||
@@ -45,7 +44,7 @@ type User struct {
|
||||
|
||||
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
|
||||
if u.BankAccount != nil && u.BankAccount.MandateReference == "" {
|
||||
u.BankAccount.MandateReference = u.GenerateMandateReference()
|
||||
u.BankAccount.MandateReference = u.BankAccount.GenerateMandateReference(u.ID)
|
||||
u.BankAccount.Update(tx)
|
||||
}
|
||||
return nil
|
||||
@@ -53,38 +52,16 @@ func (u *User) AfterCreate(tx *gorm.DB) (err error) {
|
||||
|
||||
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) GenerateMandateReference() string {
|
||||
return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN)
|
||||
}
|
||||
|
||||
func (u *User) SetPassword(plaintextPassword string) error {
|
||||
if plaintextPassword == "" {
|
||||
return nil
|
||||
if u.Password != "" {
|
||||
hash, err := argon2id.CreateHash(u.Password, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hash
|
||||
}
|
||||
|
||||
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
|
||||
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
|
||||
}
|
||||
|
||||
func (u *User) PasswordExists() bool {
|
||||
return u.Password != ""
|
||||
}
|
||||
|
||||
func (u *User) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&User{}, "id = ?", u.ID).Error
|
||||
}
|
||||
|
||||
func (u *User) Create(db *gorm.DB) error {
|
||||
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -96,118 +73,6 @@ func (u *User) Create(db *gorm.DB) error {
|
||||
})
|
||||
}
|
||||
|
||||
// return db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
// // Initialize slices/pointers if nil
|
||||
// if u.Verifications == nil {
|
||||
// u.Verifications = &[]Verification{}
|
||||
// }
|
||||
|
||||
// // Create base user first
|
||||
// if err := tx.Omit(clause.Associations).Create(u).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create user: %w", err)
|
||||
// }
|
||||
|
||||
// // Handle BankAccount
|
||||
// if u.BankAccount != (BankAccount{}) {
|
||||
// u.BankAccount.MandateReference = u.GenerateMandateReference()
|
||||
// if err := tx.Create(&u.BankAccount).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create bank account: %w", err)
|
||||
// }
|
||||
// if err := tx.Model(u).Update("bank_account_id", u.BankAccount.ID).Error; err != nil {
|
||||
// return fmt.Errorf("failed to link bank account: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Handle Membership and SubscriptionModel
|
||||
// if u.Membership != (Membership{}) {
|
||||
// if err := tx.Create(&u.Membership).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create membership: %w", err)
|
||||
// }
|
||||
// if err := tx.Model(u).Update("membership_id", u.Membership.ID).Error; err != nil {
|
||||
// return fmt.Errorf("failed to link membership: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Handle Licence and Categories
|
||||
// if u.Licence != nil {
|
||||
// u.Licence.UserID = u.ID
|
||||
// if err := tx.Create(u.Licence).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create licence: %w", err)
|
||||
// }
|
||||
|
||||
// if len(u.Licence.Categories) > 0 {
|
||||
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
// return fmt.Errorf("failed to link categories: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if err := tx.Model(u).Update("licence_id", u.Licence.ID).Error; err != nil {
|
||||
// return fmt.Errorf("failed to link licence: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Handle Consents
|
||||
// for i := range u.Consents {
|
||||
// u.Consents[i].UserID = u.ID
|
||||
// }
|
||||
// if len(u.Consents) > 0 {
|
||||
// if err := tx.Create(&u.Consents).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create consents: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Handle Verifications
|
||||
// for i := range *u.Verifications {
|
||||
// (*u.Verifications)[i].UserID = u.ID
|
||||
// }
|
||||
// if len(*u.Verifications) > 0 {
|
||||
// if err := tx.Create(u.Verifications).Error; err != nil {
|
||||
// return fmt.Errorf("failed to create verifications: %w", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reload the complete user with all associations
|
||||
// return tx.Preload(clause.Associations).
|
||||
// Preload("Membership.SubscriptionModel").
|
||||
// Preload("Licence.Categories").
|
||||
// First(u, u.ID).Error
|
||||
// })
|
||||
// }
|
||||
|
||||
// func (u *User) Create(db *gorm.DB) error {
|
||||
// return db.Transaction(func(tx *gorm.DB) error {
|
||||
// // Create the base User record (omit associations to handle them separately)
|
||||
// if err := tx.Create(u).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// for i := range u.Consents {
|
||||
// u.Consents[i].UserID = u.ID
|
||||
// }
|
||||
|
||||
// for i := range *u.Verifications {
|
||||
// (*u.Verifications)[i].UserID = u.ID
|
||||
// }
|
||||
|
||||
// if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(u).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // Replace associated Categories (assumes Categories already exist)
|
||||
// if u.Licence != nil && len(u.Licence.Categories) > 0 {
|
||||
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// logger.Info.Printf("user created: %#v", u.Safe())
|
||||
// // Preload all associations to return the fully populated User
|
||||
// return tx.
|
||||
// Preload(clause.Associations).
|
||||
// Preload("Membership.SubscriptionModel").
|
||||
// Preload("Licence.Categories").
|
||||
// First(u, u.ID).Error // Refresh the user object with all associations
|
||||
// })
|
||||
// }
|
||||
|
||||
func (u *User) Update(db *gorm.DB) error {
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -220,7 +85,7 @@ func (u *User) Update(db *gorm.DB) error {
|
||||
return err
|
||||
}
|
||||
// Update the user's main fields
|
||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Verifications", "Licence.Categories").Updates(u)
|
||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Verifications", "Licence.Categories").Updates(u)
|
||||
if result.Error != nil {
|
||||
logger.Error.Printf("User update error in update user: %#v", result.Error)
|
||||
return result.Error
|
||||
@@ -229,57 +94,6 @@ func (u *User) Update(db *gorm.DB) error {
|
||||
return errors.ErrNoRowsAffected
|
||||
}
|
||||
|
||||
if u.Password != "" {
|
||||
if err := tx.Model(&existingUser).
|
||||
Update("Password", u.Password).Error; err != nil {
|
||||
logger.Error.Printf("Password update error in update user: %#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// // Update the Membership if provided
|
||||
// if u.Membership.ID != 0 {
|
||||
// if err := tx.Model(&existingUser.Membership).Updates(u.Membership).Where("id = ?", existingUser.Membership.ID).Error; err != nil {
|
||||
// logger.Error.Printf("Membership update error in update user: %#v", err)
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if u.Licence != nil {
|
||||
// u.Licence.UserID = existingUser.ID
|
||||
|
||||
// if err := tx.Save(u.Licence).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if u.Licence != nil {
|
||||
// if existingUser.Licence == nil || existingUser.LicenceID == 0 {
|
||||
// u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User
|
||||
// if err := tx.Create(u.Licence).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// existingUser.Licence = u.Licence
|
||||
// existingUser.LicenceID = u.Licence.ID
|
||||
// if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if err := tx.Model(existingUser.Licence).Updates(u.Licence).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // Update Categories association
|
||||
// if err := tx.Model(existingUser.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
if u.Verifications != nil {
|
||||
if err := tx.Save(u.Verifications).Error; err != nil {
|
||||
return err
|
||||
@@ -300,16 +114,20 @@ func (u *User) Update(db *gorm.DB) error {
|
||||
|
||||
return db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Membership.Subscription").
|
||||
Preload("Licence.Categories").
|
||||
First(&u, u.ID).Error
|
||||
}
|
||||
|
||||
func (u *User) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&User{}, "id = ?", u.ID).Error
|
||||
}
|
||||
|
||||
func (u *User) FromID(db *gorm.DB, userID *uint) error {
|
||||
var user User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Membership.Subscription").
|
||||
Preload("Licence.Categories").
|
||||
First(&user, userID)
|
||||
if result.Error != nil {
|
||||
@@ -326,7 +144,7 @@ func (u *User) FromEmail(db *gorm.DB, email *string) error {
|
||||
var user User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Membership.Subscription").
|
||||
Preload("Licence.Categories").
|
||||
Where("email = ?", email).First(&user)
|
||||
if result.Error != nil {
|
||||
@@ -354,6 +172,14 @@ func (u *User) FromContext(db *gorm.DB, c *gin.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
|
||||
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
|
||||
}
|
||||
|
||||
func (u *User) PasswordExists() bool {
|
||||
return u.Password != ""
|
||||
}
|
||||
|
||||
func (u *User) IsVerified() bool {
|
||||
return u.Status > constants.DisabledStatus
|
||||
}
|
||||
@@ -378,24 +204,20 @@ func (u *User) SetVerification(verificationType string) (*Verification, error) {
|
||||
if u.Verifications == nil {
|
||||
u.Verifications = []Verification{}
|
||||
}
|
||||
token, err := utils.GenerateVerificationToken()
|
||||
v, err := CreateVerification(verificationType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := Verification{
|
||||
UserID: u.ID,
|
||||
VerificationToken: token,
|
||||
Type: verificationType,
|
||||
}
|
||||
v.UserID = u.ID
|
||||
if vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool { return vsl.Type == v.Type }); vi > -1 {
|
||||
u.Verifications[vi] = v
|
||||
u.Verifications[vi] = *v
|
||||
} else {
|
||||
u.Verifications = append(u.Verifications, v)
|
||||
u.Verifications = append(u.Verifications, *v)
|
||||
}
|
||||
return &v, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (u *User) GetVerification(verificationType string) (*Verification, error) {
|
||||
func (u *User) FindVerification(verificationType string) (*Verification, error) {
|
||||
if u.Verifications == nil {
|
||||
return nil, errors.ErrNoData
|
||||
}
|
||||
@@ -406,10 +228,10 @@ func (u *User) GetVerification(verificationType string) (*Verification, error) {
|
||||
return &u.Verifications[vi], nil
|
||||
}
|
||||
|
||||
func (u *User) Verify(token string, verificationType string) bool {
|
||||
func (u *User) Verify(token string, verificationType string) error {
|
||||
if token == "" || verificationType == "" {
|
||||
logger.Error.Printf("token or verification type are empty in user.Verify")
|
||||
return false
|
||||
return errors.ErrNoData
|
||||
}
|
||||
|
||||
vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool {
|
||||
@@ -418,17 +240,9 @@ func (u *User) Verify(token string, verificationType string) bool {
|
||||
|
||||
if vi == -1 {
|
||||
logger.Error.Printf("Couldn't find verification in users verifications")
|
||||
return false
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
|
||||
if u.Verifications[vi].VerifiedAt != nil {
|
||||
logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", u.Verifications[vi])
|
||||
return false
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
u.Verifications[vi].VerifiedAt = &t
|
||||
return true
|
||||
return u.Verifications[vi].Validate()
|
||||
}
|
||||
|
||||
func (u *User) Safe() map[string]interface{} {
|
||||
@@ -442,14 +256,14 @@ func (u *User) Safe() map[string]interface{} {
|
||||
"end_date": u.Membership.EndDate,
|
||||
"status": u.Membership.Status,
|
||||
"subscription": map[string]interface{}{
|
||||
"id": u.Membership.SubscriptionModel.ID,
|
||||
"name": u.Membership.SubscriptionModel.Name,
|
||||
"details": u.Membership.SubscriptionModel.Details,
|
||||
"conditions": u.Membership.SubscriptionModel.Conditions,
|
||||
"monthly_fee": u.Membership.SubscriptionModel.MonthlyFee,
|
||||
"hourly_rate": u.Membership.SubscriptionModel.HourlyRate,
|
||||
"included_per_year": u.Membership.SubscriptionModel.IncludedPerYear,
|
||||
"included_per_month": u.Membership.SubscriptionModel.IncludedPerMonth,
|
||||
"id": u.Membership.Subscription.ID,
|
||||
"name": u.Membership.Subscription.Name,
|
||||
"details": u.Membership.Subscription.Details,
|
||||
"conditions": u.Membership.Subscription.Conditions,
|
||||
"monthly_fee": u.Membership.Subscription.MonthlyFee,
|
||||
"hourly_rate": u.Membership.Subscription.HourlyRate,
|
||||
"included_per_year": u.Membership.Subscription.IncludedPerYear,
|
||||
"included_per_month": u.Membership.Subscription.IncludedPerMonth,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -543,7 +357,7 @@ func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
|
||||
var users []User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Membership.Subscription").
|
||||
Preload("Licence.Categories").
|
||||
Where(where).Find(&users)
|
||||
if result.Error != nil {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
@@ -46,3 +48,31 @@ func (v *Verification) Update(db *gorm.DB) error {
|
||||
func (v *Verification) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&v).Error
|
||||
}
|
||||
|
||||
func (v *Verification) Validate() error {
|
||||
if v.VerifiedAt != nil {
|
||||
return errors.ErrAlreadyVerified
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
v.VerifiedAt = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateVerification(verificationType string) (*Verification, error) {
|
||||
token, err := GenerateVerificationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := Verification{
|
||||
UserID: 0,
|
||||
VerificationToken: token,
|
||||
Type: verificationType,
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func GenerateVerificationToken() (string, error) {
|
||||
return utils.GenerateRandomString(32)
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"GoMembership/internal/database"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"GoMembership/internal/models"
|
||||
)
|
||||
|
||||
type SubscriptionModelsRepositoryInterface interface {
|
||||
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error)
|
||||
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
|
||||
GetSubscriptionModelNames() ([]string, error)
|
||||
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
|
||||
// GetUsersBySubscription(id uint) (*[]models.SubscriptionModel, error)
|
||||
DeleteSubscription(id *uint) error
|
||||
}
|
||||
|
||||
type SubscriptionModelsRepository struct{}
|
||||
|
||||
func (sr *SubscriptionModelsRepository) CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error) {
|
||||
|
||||
result := database.DB.Create(subscriptionModel)
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return subscriptionModel.ID, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionModelsRepository) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
|
||||
|
||||
result := database.DB.Model(&models.SubscriptionModel{ID: subscription.ID}).Updates(subscription)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionModelsRepository) DeleteSubscription(id *uint) error {
|
||||
|
||||
result := database.DB.Delete(&models.SubscriptionModel{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
|
||||
var model models.SubscriptionModel
|
||||
result := database.DB.Where("name = ?", modelname).First(&model)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return &model, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionModelsRepository) GetSubscriptionModelNames() ([]string, error) {
|
||||
var names []string
|
||||
if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionModelsRepository) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
|
||||
var subscriptions []models.SubscriptionModel
|
||||
result := database.DB.Where(where).Find(&subscriptions)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, result.Error
|
||||
}
|
||||
return &subscriptions, nil
|
||||
}
|
||||
|
||||
func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
|
||||
var users []models.User
|
||||
|
||||
err := database.DB.Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("BankAccount").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Joins("JOIN memberships ON users.id = memberships.user_id").
|
||||
Joins("JOIN subscription_models ON memberships.subscription_model_id = subscription_models.id").
|
||||
Where("subscription_models.id = ?", subscriptionID).
|
||||
Find(&users).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &users, nil
|
||||
|
||||
}
|
||||
97
go-backend/internal/repositories/subscription_repository.go
Normal file
97
go-backend/internal/repositories/subscription_repository.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"GoMembership/internal/database"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"GoMembership/internal/models"
|
||||
)
|
||||
|
||||
type SubscriptionsRepositoryInterface interface {
|
||||
CreateSubscription(subscription *models.Subscription) (uint, error)
|
||||
UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error)
|
||||
GetSubscriptionNames() ([]string, error)
|
||||
GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error)
|
||||
// GetUsersBySubscription(id uint) (*[]models.Subscription, error)
|
||||
DeleteSubscription(id *uint) error
|
||||
}
|
||||
|
||||
type SubscriptionsRepository struct{}
|
||||
|
||||
func (sr *SubscriptionsRepository) CreateSubscription(subscription *models.Subscription) (uint, error) {
|
||||
|
||||
result := database.DB.Create(subscription)
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return subscription.ID, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionsRepository) UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error) {
|
||||
|
||||
result := database.DB.Model(&models.Subscription{ID: subscription.ID}).Updates(subscription)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionsRepository) DeleteSubscription(id *uint) error {
|
||||
|
||||
result := database.DB.Delete(&models.Subscription{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSubscriptionByName(modelname *string) (*models.Subscription, error) {
|
||||
var model models.Subscription
|
||||
result := database.DB.Where("name = ?", modelname).First(&model)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return &model, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionsRepository) GetSubscriptionNames() ([]string, error) {
|
||||
var names []string
|
||||
if err := database.DB.Model(&models.Subscription{}).Pluck("name", &names).Error; err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (sr *SubscriptionsRepository) GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error) {
|
||||
var subscriptions []models.Subscription
|
||||
result := database.DB.Where(where).Find(&subscriptions)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, result.Error
|
||||
}
|
||||
return &subscriptions, nil
|
||||
}
|
||||
|
||||
func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
|
||||
var users []models.User
|
||||
|
||||
err := database.DB.Preload("Membership").
|
||||
Preload("Membership.Subscription").
|
||||
Preload("BankAccount").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Joins("JOIN memberships ON users.id = memberships.user_id").
|
||||
Joins("JOIN subscriptions ON memberships.subscription_id = subscriptions.id").
|
||||
Where("subscriptions.id = ?", subscriptionID).
|
||||
Find(&users).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &users, nil
|
||||
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func Run(db *gorm.DB) {
|
||||
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
||||
|
||||
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
|
||||
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
|
||||
var subscriptionRepo repositories.SubscriptionsRepositoryInterface = &repositories.SubscriptionsRepository{}
|
||||
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
|
||||
|
||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||
|
||||
@@ -59,8 +59,8 @@ func (s *CarService) FromID(id uint) (*models.Car, error) {
|
||||
|
||||
// GetAll retrieves all cars
|
||||
func (s *CarService) GetAll() (*[]models.Car, error) {
|
||||
var cars []models.Car
|
||||
if err := s.DB.Find(&cars).Error; err != nil {
|
||||
cars, err := models.GetAllCars(s.DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cars, nil
|
||||
|
||||
@@ -169,10 +169,10 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
|
||||
}{
|
||||
Company: user.Company,
|
||||
FirstName: user.FirstName,
|
||||
MembershipModel: user.Membership.SubscriptionModel.Name,
|
||||
MembershipModel: user.Membership.Subscription.Name,
|
||||
MembershipID: user.Membership.ID,
|
||||
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
||||
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
||||
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
|
||||
RentalFee: float32(user.Membership.Subscription.HourlyRate),
|
||||
BASEURL: config.Site.BaseURL,
|
||||
WebsiteTitle: config.Site.WebsiteTitle,
|
||||
Logo: config.Templates.LogoURI,
|
||||
@@ -216,10 +216,10 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
|
||||
Company: user.Company,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
MembershipModel: user.Membership.SubscriptionModel.Name,
|
||||
MembershipModel: user.Membership.Subscription.Name,
|
||||
MembershipID: user.Membership.ID,
|
||||
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
||||
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
||||
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
|
||||
RentalFee: float32(user.Membership.Subscription.HourlyRate),
|
||||
Address: user.Address,
|
||||
ZipCode: user.ZipCode,
|
||||
City: user.City,
|
||||
|
||||
@@ -11,17 +11,17 @@ import (
|
||||
type MembershipServiceInterface interface {
|
||||
RegisterMembership(membership *models.Membership) (uint, error)
|
||||
FindMembershipByUserID(userID uint) (*models.Membership, error)
|
||||
RegisterSubscription(subscription *models.SubscriptionModel) (uint, error)
|
||||
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
|
||||
RegisterSubscription(subscription *models.Subscription) (uint, error)
|
||||
UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error)
|
||||
DeleteSubscription(id *uint, name *string) error
|
||||
GetSubscriptionModelNames() ([]string, error)
|
||||
GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error)
|
||||
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
|
||||
GetSubscriptionNames() ([]string, error)
|
||||
GetSubscriptionByName(modelname *string) (*models.Subscription, error)
|
||||
GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error)
|
||||
}
|
||||
|
||||
type MembershipService struct {
|
||||
Repo repositories.MembershipRepositoryInterface
|
||||
SubscriptionRepo repositories.SubscriptionModelsRepositoryInterface
|
||||
SubscriptionRepo repositories.SubscriptionsRepositoryInterface
|
||||
}
|
||||
|
||||
func (service *MembershipService) RegisterMembership(membership *models.Membership) (uint, error) {
|
||||
@@ -29,7 +29,7 @@ func (service *MembershipService) RegisterMembership(membership *models.Membersh
|
||||
return service.Repo.CreateMembership(membership)
|
||||
}
|
||||
|
||||
func (service *MembershipService) UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error) {
|
||||
func (service *MembershipService) UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error) {
|
||||
|
||||
existingSubscription, err := repositories.GetSubscriptionByName(&subscription.Name)
|
||||
if err != nil {
|
||||
@@ -82,19 +82,19 @@ func (service *MembershipService) FindMembershipByUserID(userID uint) (*models.M
|
||||
}
|
||||
|
||||
// Membership_Subscriptions
|
||||
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (uint, error) {
|
||||
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
|
||||
func (service *MembershipService) RegisterSubscription(subscription *models.Subscription) (uint, error) {
|
||||
return service.SubscriptionRepo.CreateSubscription(subscription)
|
||||
}
|
||||
|
||||
func (service *MembershipService) GetSubscriptionModelNames() ([]string, error) {
|
||||
return service.SubscriptionRepo.GetSubscriptionModelNames()
|
||||
func (service *MembershipService) GetSubscriptionNames() ([]string, error) {
|
||||
return service.SubscriptionRepo.GetSubscriptionNames()
|
||||
}
|
||||
|
||||
func (service *MembershipService) GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error) {
|
||||
func (service *MembershipService) GetSubscriptionByName(modelname *string) (*models.Subscription, error) {
|
||||
return repositories.GetSubscriptionByName(modelname)
|
||||
}
|
||||
|
||||
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
|
||||
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error) {
|
||||
if where == nil {
|
||||
where = map[string]interface{}{}
|
||||
}
|
||||
|
||||
@@ -74,15 +74,13 @@ func (s *UserService) Update(user *models.User) (*models.User, error) {
|
||||
}
|
||||
user.BankAccount.ID = existingUser.BankAccount.ID
|
||||
|
||||
user.SetPassword(user.Password)
|
||||
|
||||
// Validate subscription model
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||
if err != nil {
|
||||
return nil, errors.ErrSubscriptionNotFound
|
||||
}
|
||||
user.Membership.SubscriptionModel = *selectedModel
|
||||
user.Membership.SubscriptionModelID = selectedModel.ID
|
||||
user.Membership.Subscription = *selectedModel
|
||||
user.Membership.SubscriptionID = selectedModel.ID
|
||||
|
||||
if err := user.Update(s.DB); err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -97,14 +95,13 @@ func (s *UserService) Update(user *models.User) (*models.User, error) {
|
||||
}
|
||||
|
||||
func (s *UserService) Register(user *models.User) (id uint, token string, err error) {
|
||||
user.SetPassword(user.Password)
|
||||
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||
if err != nil {
|
||||
return 0, "", errors.ErrSubscriptionNotFound
|
||||
}
|
||||
user.Membership.SubscriptionModel = *selectedModel
|
||||
user.Membership.SubscriptionModelID = selectedModel.ID
|
||||
user.Membership.Subscription = *selectedModel
|
||||
user.Membership.SubscriptionID = selectedModel.ID
|
||||
user.Status = constants.UnverifiedStatus
|
||||
user.BankAccount.MandateDateSigned = time.Now()
|
||||
v, err := user.SetVerification(constants.VerificationTypes.Email)
|
||||
|
||||
@@ -30,10 +30,6 @@ func GenerateRandomString(length int) (string, error) {
|
||||
return base64.URLEncoding.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
func GenerateVerificationToken() (string, error) {
|
||||
return GenerateRandomString(32)
|
||||
}
|
||||
|
||||
func DecodeMail(message string) (*Email, error) {
|
||||
msg, err := mail.ReadMessage(strings.NewReader(message))
|
||||
if err != nil {
|
||||
|
||||
@@ -44,7 +44,7 @@ func HandleUserUpdateError(c *gin.Context, err error) {
|
||||
case errors.ErrDuplicateEntry:
|
||||
RespondWithError(c, err, "User Unique constraint failed", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.Duplicate)
|
||||
case errors.ErrSubscriptionNotFound:
|
||||
RespondWithError(c, err, "Couldn't find subscription", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
|
||||
RespondWithError(c, err, "Couldn't find subscription", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
|
||||
default:
|
||||
RespondWithError(c, err, "Couldn't update user", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
||||
}
|
||||
@@ -53,30 +53,30 @@ func HandleUserUpdateError(c *gin.Context, err error) {
|
||||
func HandleSubscriptionDeleteError(c *gin.Context, err error) {
|
||||
switch err {
|
||||
case errors.ErrNoData:
|
||||
RespondWithError(c, err, "Missing subscription name during deletion", http.StatusExpectationFailed, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
|
||||
RespondWithError(c, err, "Missing subscription name during deletion", http.StatusExpectationFailed, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
|
||||
case errors.ErrSubscriptionNotFound:
|
||||
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
|
||||
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
|
||||
case errors.ErrInvalidSubscriptionData:
|
||||
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
|
||||
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
|
||||
case errors.ErrSubscriptionInUse:
|
||||
RespondWithError(c, err, "Subscription is in use by at least one user", http.StatusExpectationFailed, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InUse)
|
||||
RespondWithError(c, err, "Subscription is in use by at least one user", http.StatusExpectationFailed, errors.Responses.Fields.Subscription, errors.Responses.Keys.InUse)
|
||||
default:
|
||||
RespondWithError(c, err, "Error during subscription Deletion", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
|
||||
RespondWithError(c, err, "Error during subscription Deletion", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSubscriptionUpdateError(c *gin.Context, err error) {
|
||||
|
||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||
RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Duplicate)
|
||||
RespondWithError(c, err, "Subscription already exists", http.StatusConflict, errors.Responses.Fields.Subscription, errors.Responses.Keys.Duplicate)
|
||||
} else {
|
||||
switch err {
|
||||
case errors.ErrSubscriptionNotFound:
|
||||
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.NotFound)
|
||||
RespondWithError(c, err, "Subscription not found", http.StatusNotFound, errors.Responses.Fields.Subscription, errors.Responses.Keys.NotFound)
|
||||
case errors.ErrInvalidSubscriptionData:
|
||||
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.Invalid)
|
||||
RespondWithError(c, err, "Invalid subscription data", http.StatusBadRequest, errors.Responses.Fields.Subscription, errors.Responses.Keys.Invalid)
|
||||
default:
|
||||
RespondWithError(c, err, "Couldn't update subscription", http.StatusInternalServerError, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InternalServerError)
|
||||
RespondWithError(c, err, "Couldn't update subscription", http.StatusInternalServerError, errors.Responses.Fields.Subscription, errors.Responses.Keys.InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func validateMembership(db *gorm.DB, user *models.User, sl validator.StructLevel) {
|
||||
if user.Membership.SubscriptionModel.RequiredMembershipField != "" {
|
||||
switch user.Membership.SubscriptionModel.RequiredMembershipField {
|
||||
if user.Membership.Subscription.RequiredMembershipField != "" {
|
||||
switch user.Membership.Subscription.RequiredMembershipField {
|
||||
case "ParentMembershipID":
|
||||
if err := CheckParentMembershipID(db, user); err != nil {
|
||||
logger.Error.Printf("Error ParentMembershipValidation: %v", err.Error())
|
||||
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.SubscriptionModel.RequiredMembershipField,
|
||||
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
|
||||
"RequiredMembershipField", "invalid", "")
|
||||
}
|
||||
default:
|
||||
logger.Error.Printf("Error no matching RequiredMembershipField: %v", errors.ErrInvalidValue.Error())
|
||||
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.SubscriptionModel.RequiredMembershipField,
|
||||
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
|
||||
"RequiredMembershipField", "not_implemented", "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@ func SetupValidators(db *gorm.DB) {
|
||||
|
||||
// Register struct-level validations
|
||||
v.RegisterStructValidation(ValidateUserFactory(db), models.User{})
|
||||
v.RegisterStructValidation(ValidateSubscription, models.SubscriptionModel{})
|
||||
v.RegisterStructValidation(ValidateSubscription, models.Subscription{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,17 @@ package validation
|
||||
import (
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/internal/repositories"
|
||||
"GoMembership/pkg/logger"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// ValidateNewSubscription validates a new subscription model being created
|
||||
func ValidateSubscription(sl validator.StructLevel) {
|
||||
subscription := sl.Current().Interface().(models.SubscriptionModel)
|
||||
subscription := sl.Current().Interface().(models.Subscription)
|
||||
|
||||
if subscription.Name == "" {
|
||||
sl.ReportError(subscription.Name, "Name", "name", "required", "")
|
||||
}
|
||||
logger.Error.Printf("parent.type.name: %#v", sl.Parent().Type().Name())
|
||||
if sl.Parent().Type().Name() == "" {
|
||||
// This is modifying a subscription directly
|
||||
if subscription.Details == "" {
|
||||
|
||||
@@ -30,16 +30,16 @@ func ValidateUserFactory(db *gorm.DB) validator.StructLevelFunc {
|
||||
func validateUser(db *gorm.DB, sl validator.StructLevel) {
|
||||
user := sl.Current().Interface().(models.User)
|
||||
|
||||
// validate subscriptionModel
|
||||
if user.Membership.SubscriptionModel.Name == "" {
|
||||
sl.ReportError(user.Membership.SubscriptionModel.Name, "subscription.name", "name", "required", "")
|
||||
// validate subscription
|
||||
if user.Membership.Subscription.Name == "" {
|
||||
sl.ReportError(user.Membership.Subscription.Name, "subscription.name", "name", "required", "")
|
||||
} else {
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
|
||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||
if err != nil {
|
||||
logger.Error.Printf("Error finding subscription model for user %v: %v", user.Email, err)
|
||||
sl.ReportError(user.Membership.SubscriptionModel.Name, "subscription.name", "name", "invalid", "")
|
||||
sl.ReportError(user.Membership.Subscription.Name, "subscription.name", "name", "invalid", "")
|
||||
} else {
|
||||
user.Membership.SubscriptionModel = *selectedModel
|
||||
user.Membership.Subscription = *selectedModel
|
||||
}
|
||||
}
|
||||
if user.IsSupporter() {
|
||||
|
||||
@@ -31,7 +31,7 @@ type ValidationKeys struct {
|
||||
InternalServerError string
|
||||
InvalidJSON string
|
||||
InvalidUserID string
|
||||
InvalidSubscriptionModel string
|
||||
InvalidSubscription string
|
||||
Unauthorized string
|
||||
UserNotFoundWrongPassword string
|
||||
JwtGenerationFailed string
|
||||
@@ -48,7 +48,7 @@ type ValidationKeys struct {
|
||||
type ValidationFields struct {
|
||||
General string
|
||||
ParentMembershipID string
|
||||
SubscriptionModel string
|
||||
Subscription string
|
||||
Login string
|
||||
Email string
|
||||
User string
|
||||
@@ -66,7 +66,7 @@ var Responses = struct {
|
||||
InternalServerError: "server.error.internal_server_error",
|
||||
InvalidJSON: "server.error.invalid_json",
|
||||
InvalidUserID: "server.validation.invalid_user_id",
|
||||
InvalidSubscriptionModel: "server.validation.invalid_subscription",
|
||||
InvalidSubscription: "server.validation.invalid_subscription",
|
||||
Unauthorized: "server.error.unauthorized",
|
||||
UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password",
|
||||
JwtGenerationFailed: "server.error.jwt_generation_failed",
|
||||
@@ -82,7 +82,7 @@ var Responses = struct {
|
||||
Fields: ValidationFields{
|
||||
General: "server.general",
|
||||
ParentMembershipID: "parent_membership_id",
|
||||
SubscriptionModel: "subscription",
|
||||
Subscription: "subscription",
|
||||
Login: "user.login",
|
||||
Email: "user.email",
|
||||
User: "user.user",
|
||||
|
||||
Reference in New Issue
Block a user