Compare commits
60 Commits
8d56a9ad48
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d25fa005c | ||
|
|
20a693a80c | ||
|
|
242d37713d | ||
|
|
4b83be6ed8 | ||
|
|
8fcb73f24d | ||
|
|
11740cb503 | ||
|
|
06f8078b17 | ||
|
|
18f5dadb06 | ||
|
|
87f08dd3be | ||
|
|
2af4575ff2 | ||
|
|
560623788a | ||
|
|
5d55f5a8d9 | ||
|
|
28dfe7ecde | ||
|
|
741145b960 | ||
|
|
490b29295f | ||
|
|
ded5e6ceb1 | ||
|
|
b81804439e | ||
|
|
9a9af9f002 | ||
|
|
cd495584b0 | ||
|
|
bbead3c43b | ||
|
|
ce18324391 | ||
|
|
c9d5a88dbf | ||
|
|
380fee09c1 | ||
|
|
e35524132e | ||
|
|
af000aa4bc | ||
|
|
90ed0925ca | ||
|
|
7c0a6fedb5 | ||
|
|
0e6edc8e65 | ||
|
|
45a219625a | ||
|
|
ee10389f1d | ||
|
|
073d353764 | ||
|
|
9d2b33f832 | ||
|
|
e60aaa1d69 | ||
|
|
ca99e28433 | ||
|
|
9427492cb1 | ||
|
|
60d3f075bf | ||
|
|
d473aef3a9 | ||
|
|
ef4d3c9576 | ||
|
|
9a8b386931 | ||
|
|
9c429185dc | ||
|
|
c7865d0582 | ||
|
|
feb8abcc42 | ||
|
|
c8d0904fd7 | ||
|
|
294ad76e4b | ||
|
|
ca441d51e7 | ||
|
|
39c060794a | ||
|
|
c6ea179eca | ||
|
|
0d6013d566 | ||
|
|
0c3204df15 | ||
|
|
cfc10ab087 | ||
|
|
df6125b7cb | ||
|
|
7af66ee9de | ||
|
|
b2b702c21d | ||
|
|
fa996692fe | ||
|
|
8258a7c2a3 | ||
|
|
37ccbaaba4 | ||
|
|
c810e48451 | ||
|
|
8f737282f2 | ||
|
|
3756205ad4 | ||
|
|
68851c6257 |
58
frontend/src/app.d.ts
vendored
58
frontend/src/app.d.ts
vendored
@@ -17,7 +17,7 @@ interface Membership {
|
|||||||
start_date: string | '';
|
start_date: string | '';
|
||||||
end_date: string | '';
|
end_date: string | '';
|
||||||
parent_member_id: number | -1;
|
parent_member_id: number | -1;
|
||||||
subscription_model: Subscription;
|
subscription: Subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BankAccount {
|
interface BankAccount {
|
||||||
@@ -51,7 +51,6 @@ interface User {
|
|||||||
last_name: string | '';
|
last_name: string | '';
|
||||||
password: string | '';
|
password: string | '';
|
||||||
phone: string | '';
|
phone: string | '';
|
||||||
notes: string | '';
|
|
||||||
address: string | '';
|
address: string | '';
|
||||||
zip_code: string | '';
|
zip_code: string | '';
|
||||||
city: string | '';
|
city: string | '';
|
||||||
@@ -60,11 +59,51 @@ interface User {
|
|||||||
role_id: number | -1;
|
role_id: number | -1;
|
||||||
dateofbirth: string | '';
|
dateofbirth: string | '';
|
||||||
company: string | '';
|
company: string | '';
|
||||||
profile_picture: string | '';
|
membership: Membership | null;
|
||||||
payment_status: number | -1;
|
bank_account: BankAccount | null;
|
||||||
membership: Membership;
|
licence: Licence | null;
|
||||||
bank_account: BankAccount;
|
notes: string | '';
|
||||||
licence: Licence;
|
}
|
||||||
|
|
||||||
|
interface Car {
|
||||||
|
id: number | -1;
|
||||||
|
name: string | '';
|
||||||
|
status: number | 0;
|
||||||
|
brand: string | '';
|
||||||
|
model: string | '';
|
||||||
|
price: number | 0;
|
||||||
|
rate: number | 0;
|
||||||
|
start_date: string | '';
|
||||||
|
end_date: string | '';
|
||||||
|
color: string | '';
|
||||||
|
licence_plate: string | '';
|
||||||
|
location: Location | null;
|
||||||
|
damages: Damage[] | null;
|
||||||
|
insurances: Insurance[] | null;
|
||||||
|
notes: string | '';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Location {
|
||||||
|
latitude: number | 0;
|
||||||
|
longitude: number | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Damage {
|
||||||
|
id: number | -1;
|
||||||
|
name: string | '';
|
||||||
|
opponent: User | null;
|
||||||
|
driver_id: number | -1;
|
||||||
|
insurance: Insurance | null;
|
||||||
|
date: string | '';
|
||||||
|
notes: string | '';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Insurance {
|
||||||
|
id: number | -1;
|
||||||
|
company: string | '';
|
||||||
|
reference: string | '';
|
||||||
|
start_date: string | '';
|
||||||
|
end_date: string | '';
|
||||||
notes: string | '';
|
notes: string | '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +113,7 @@ declare global {
|
|||||||
interface Locals {
|
interface Locals {
|
||||||
user: User;
|
user: User;
|
||||||
users: User[];
|
users: User[];
|
||||||
|
cars: Cars[];
|
||||||
subscriptions: Subscription[];
|
subscriptions: Subscription[];
|
||||||
licence_categories: LicenceCategory[];
|
licence_categories: LicenceCategory[];
|
||||||
}
|
}
|
||||||
@@ -84,6 +124,10 @@ declare global {
|
|||||||
licence: Licence;
|
licence: Licence;
|
||||||
licenceCategory: LicenceCategory;
|
licenceCategory: LicenceCategory;
|
||||||
bankAccount: BankAccount;
|
bankAccount: BankAccount;
|
||||||
|
car: Car;
|
||||||
|
insurance: Insurance;
|
||||||
|
location: Location;
|
||||||
|
damage: Damage;
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|||||||
682
frontend/src/lib/components/CarEditForm.svelte
Normal file
682
frontend/src/lib/components/CarEditForm.svelte
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
<script>
|
||||||
|
import InputField from '$lib/components/InputField.svelte';
|
||||||
|
import SmallLoader from '$lib/components/SmallLoader.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { applyAction, enhance } from '$app/forms';
|
||||||
|
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
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();
|
||||||
|
|
||||||
|
/** @type {import('../../routes/auth/about/[id]/$types').ActionData} */
|
||||||
|
export let form;
|
||||||
|
|
||||||
|
/** @type {App.Locals['user'] } */
|
||||||
|
export let editor;
|
||||||
|
|
||||||
|
/** @type {App.Locals['users'] } */
|
||||||
|
export let users;
|
||||||
|
|
||||||
|
/** @type {App.Types['car']} */
|
||||||
|
export let car;
|
||||||
|
|
||||||
|
$: 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('@sveltejs/kit').SubmitFunction} */
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
isUpdating = true;
|
||||||
|
return async ({ result }) => {
|
||||||
|
isUpdating = false;
|
||||||
|
if (result.type === 'success' || result.type === 'redirect') {
|
||||||
|
dispatch('close');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
await applyAction(result);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isLoading}
|
||||||
|
<SmallLoader width={30} message={$t('loading.car_data')} />
|
||||||
|
{:else if editor && car}
|
||||||
|
<form class="content" action="?/updateCar" method="POST" use:enhance={handleUpdate}>
|
||||||
|
<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>
|
||||||
|
{#if form?.errors}
|
||||||
|
{#each form?.errors as error (error.id)}
|
||||||
|
<h4
|
||||||
|
class="step-subtitle warning"
|
||||||
|
in:receive|global={{ key: error.id }}
|
||||||
|
out:send|global={{ key: error.id }}
|
||||||
|
>
|
||||||
|
{$t(error.field) + ': ' + $t(error.key)}
|
||||||
|
</h4>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
<div class="button-container">
|
||||||
|
{#each TABS as tab}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button-dark"
|
||||||
|
class:active={activeTab === tab}
|
||||||
|
on:click={() => (activeTab = tab)}
|
||||||
|
>
|
||||||
|
{$t(tab)}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" style="display: {activeTab === 'car.car' ? 'block' : 'none'}">
|
||||||
|
<InputField
|
||||||
|
name="car[name]"
|
||||||
|
label={$t('name')}
|
||||||
|
bind:value={car.name}
|
||||||
|
placeholder={$t('placeholder.car_name')}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[brand]"
|
||||||
|
label={$t('car.brand')}
|
||||||
|
bind:value={car.brand}
|
||||||
|
placeholder={$t('placeholder.car_brand')}
|
||||||
|
required={true}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[model]"
|
||||||
|
label={$t('car.model')}
|
||||||
|
bind:value={car.model}
|
||||||
|
placeholder={$t('placeholder.car_model')}
|
||||||
|
required={true}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[color]"
|
||||||
|
label={$t('color')}
|
||||||
|
bind:value={car.color}
|
||||||
|
placeholder={$t('placeholder.car_color')}
|
||||||
|
required={true}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[licence_plate]"
|
||||||
|
label={$t('car.licence_plate')}
|
||||||
|
bind:value={car.licence_plate}
|
||||||
|
placeholder={$t('placeholder.car_licence_plate')}
|
||||||
|
required={true}
|
||||||
|
toUpperCase={true}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[price]"
|
||||||
|
type="number"
|
||||||
|
label={$t('price')}
|
||||||
|
bind:value={car.price}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[rate]"
|
||||||
|
type="number"
|
||||||
|
label={$t('car.leasing_rate')}
|
||||||
|
bind:value={car.rate}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[start_date]"
|
||||||
|
type="date"
|
||||||
|
label={$t('car.start_date')}
|
||||||
|
bind:value={car.start_date}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[end_date]"
|
||||||
|
type="date"
|
||||||
|
label={$t('car.end_date')}
|
||||||
|
bind:value={car.end_date}
|
||||||
|
readonly={readonlyUser}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="car[notes]"
|
||||||
|
type="textarea"
|
||||||
|
label={$t('notes')}
|
||||||
|
bind:value={car.notes}
|
||||||
|
placeholder={$t('placeholder.notes', {
|
||||||
|
values: { name: car.name || car.brand + ' ' + car.model }
|
||||||
|
})}
|
||||||
|
rows={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" style="display: {activeTab === 'insurance' ? 'block' : 'none'}">
|
||||||
|
<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}
|
||||||
|
<SmallLoader width={30} message={$t('loading.updating')} />
|
||||||
|
{:else}
|
||||||
|
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
|
||||||
|
{$t('cancel')}</button
|
||||||
|
>
|
||||||
|
<button type="submit" class="button-dark">{$t('confirm')}</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</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;
|
||||||
|
background-color: var(--surface0);
|
||||||
|
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;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container button {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: calc(50% - 5px);
|
||||||
|
background-color: var(--surface1);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--overlay0);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container button:hover {
|
||||||
|
background-color: var(--surface2);
|
||||||
|
border-color: var(--lavender);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.button-container button {
|
||||||
|
flex-basis: 100%;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
let inputValue = target.value;
|
let inputValue = target.value;
|
||||||
if (toUpperCase) {
|
if (toUpperCase) {
|
||||||
inputValue = inputValue.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +132,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if readonly}
|
{#if readonly}
|
||||||
<input {name} type="hidden" bind:value />
|
<input {name} type="hidden" bind:value />
|
||||||
{/if}
|
|
||||||
{#if type === 'select'}
|
<span class="label"
|
||||||
|
>{type == 'select' && typeof value === 'number' ? options[value].label : value}</span
|
||||||
|
>
|
||||||
|
{:else if type === 'select'}
|
||||||
<select
|
<select
|
||||||
{name}
|
{name}
|
||||||
bind:value
|
bind:value
|
||||||
@@ -176,7 +179,7 @@
|
|||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--form-control-color: var(--green); /* Changed from #6bff55 */
|
--form-control-color: var(--green); /* Changed from #6bff55 */
|
||||||
--form-control-disabled: var(--overlay0); /* Changed from #959495 */
|
--form-control-disabled: var(--subtext1); /* Changed from #959495 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { applyAction, enhance } from '$app/forms';
|
import { applyAction, enhance } from '$app/forms';
|
||||||
import { receive, send } from '$lib/utils/helpers';
|
import { receive, send } from '$lib/utils/helpers';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
import { defaultSubscription } from '$lib/utils/defaults';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@@ -17,20 +18,8 @@
|
|||||||
/** @type {App.Types['subscription'] | null} */
|
/** @type {App.Types['subscription'] | null} */
|
||||||
export let subscription;
|
export let subscription;
|
||||||
|
|
||||||
/** @type {App.Types['subscription']} */
|
|
||||||
const blankSubscription = {
|
|
||||||
id: 0,
|
|
||||||
name: '',
|
|
||||||
details: '',
|
|
||||||
conditions: '',
|
|
||||||
monthly_fee: 0,
|
|
||||||
hourly_rate: 0,
|
|
||||||
included_hours_per_year: 0,
|
|
||||||
included_hours_per_month: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Opening subscription modal with:', subscription);
|
console.log('Opening subscription modal with:', subscription);
|
||||||
$: subscription = subscription || { ...blankSubscription };
|
$: subscription = subscription || { ...defaultSubscription() };
|
||||||
$: isLoading = subscription === undefined || user === undefined;
|
$: isLoading = subscription === undefined || user === undefined;
|
||||||
let isUpdating = false;
|
let isUpdating = false;
|
||||||
|
|
||||||
@@ -55,7 +44,7 @@
|
|||||||
<form class="content" action="?/updateSubscription" method="POST" use:enhance={handleUpdate}>
|
<form class="content" action="?/updateSubscription" method="POST" use:enhance={handleUpdate}>
|
||||||
<input name="susbscription[id]" type="hidden" bind:value={subscription.id} />
|
<input name="susbscription[id]" type="hidden" bind:value={subscription.id} />
|
||||||
<h1 class="step-title" style="text-align: center;">
|
<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>
|
</h1>
|
||||||
{#if form?.errors}
|
{#if form?.errors}
|
||||||
{#each form?.errors as error (error.id)}
|
{#each form?.errors as error (error.id)}
|
||||||
@@ -71,7 +60,7 @@
|
|||||||
<div class="tab-content" style="display: block">
|
<div class="tab-content" style="display: block">
|
||||||
<InputField
|
<InputField
|
||||||
name="subscription[name]"
|
name="subscription[name]"
|
||||||
label={$t('subscription.name')}
|
label={$t('subscriptions.name')}
|
||||||
bind:value={subscription.name}
|
bind:value={subscription.name}
|
||||||
placeholder={$t('placeholder.subscription_name')}
|
placeholder={$t('placeholder.subscription_name')}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -88,7 +77,7 @@
|
|||||||
<InputField
|
<InputField
|
||||||
name="subscription[conditions]"
|
name="subscription[conditions]"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
label={$t('subscription.conditions')}
|
label={$t('subscriptions.conditions')}
|
||||||
bind:value={subscription.conditions}
|
bind:value={subscription.conditions}
|
||||||
placeholder={$t('placeholder.subscription_conditions')}
|
placeholder={$t('placeholder.subscription_conditions')}
|
||||||
readonly={subscription.id > 0}
|
readonly={subscription.id > 0}
|
||||||
@@ -96,7 +85,7 @@
|
|||||||
<InputField
|
<InputField
|
||||||
name="subscription[monthly_fee]"
|
name="subscription[monthly_fee]"
|
||||||
type="number"
|
type="number"
|
||||||
label={$t('subscription.monthly_fee')}
|
label={$t('subscriptions.monthly_fee')}
|
||||||
bind:value={subscription.monthly_fee}
|
bind:value={subscription.monthly_fee}
|
||||||
placeholder={$t('placeholder.subscription_monthly_fee')}
|
placeholder={$t('placeholder.subscription_monthly_fee')}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -105,7 +94,7 @@
|
|||||||
<InputField
|
<InputField
|
||||||
name="subscription[hourly_rate]"
|
name="subscription[hourly_rate]"
|
||||||
type="number"
|
type="number"
|
||||||
label={$t('subscription.hourly_rate')}
|
label={$t('subscriptions.hourly_rate')}
|
||||||
bind:value={subscription.hourly_rate}
|
bind:value={subscription.hourly_rate}
|
||||||
required={true}
|
required={true}
|
||||||
readonly={subscription.id > 0}
|
readonly={subscription.id > 0}
|
||||||
@@ -113,14 +102,14 @@
|
|||||||
<InputField
|
<InputField
|
||||||
name="subscription[included_hours_per_year]"
|
name="subscription[included_hours_per_year]"
|
||||||
type="number"
|
type="number"
|
||||||
label={$t('subscription.included_hours_per_year')}
|
label={$t('subscriptions.included_hours_per_year')}
|
||||||
bind:value={subscription.included_hours_per_year}
|
bind:value={subscription.included_hours_per_year}
|
||||||
readonly={subscription.id > 0}
|
readonly={subscription.id > 0}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="included_hours_per_month"
|
name="included_hours_per_month"
|
||||||
type="number"
|
type="number"
|
||||||
label={$t('subscription.included_hours_per_month')}
|
label={$t('subscriptions.included_hours_per_month')}
|
||||||
bind:value={subscription.included_hours_per_month}
|
bind:value={subscription.included_hours_per_month}
|
||||||
readonly={subscription.id > 0}
|
readonly={subscription.id > 0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,22 +6,29 @@
|
|||||||
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { PERMISSIONS } from '$lib/utils/constants';
|
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} */
|
/** @type {import('../../routes/auth/about/[id]/$types').ActionData} */
|
||||||
export let form;
|
export let form;
|
||||||
|
|
||||||
/** @type {App.Locals['subscriptions']}*/
|
/** @type {App.Locals['subscriptions'] | null}*/
|
||||||
export let subscriptions;
|
export let subscriptions;
|
||||||
|
|
||||||
/** @type {App.Locals['user']} */
|
/** @type {App.Locals['user']} */
|
||||||
export let user;
|
export let user;
|
||||||
|
|
||||||
// Ensure licence is initialized before passing to child
|
export let submit_form = true;
|
||||||
$: if (user && !user.licence) {
|
|
||||||
user.licence = defaultLicence();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Ensure licence is initialized before passing to child
|
||||||
|
// $: 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']} */
|
/** @type {App.Locals['user']} */
|
||||||
export let editor;
|
export let editor;
|
||||||
|
|
||||||
@@ -29,8 +36,10 @@
|
|||||||
|
|
||||||
// $: isNewUser = user === null;
|
// $: isNewUser = user === null;
|
||||||
$: isLoading = user === undefined;
|
$: isLoading = user === undefined;
|
||||||
|
$: if (user != null) {
|
||||||
/** @type {App.Locals['licence_categories']} */
|
console.log(user);
|
||||||
|
}
|
||||||
|
/** @type {App.Locals['licence_categories'] | null} */
|
||||||
export let licence_categories;
|
export let licence_categories;
|
||||||
|
|
||||||
const userStatusOptions = [
|
const userStatusOptions = [
|
||||||
@@ -41,6 +50,7 @@
|
|||||||
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
{ value: 5, label: $t('userStatus.5'), color: '--red' } // Red for "Deaktiviert"
|
||||||
];
|
];
|
||||||
const userRoleOptions = [
|
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: 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: 1, label: $t('userRole.1'), color: '--light-green' }, // Light green for "Verifiziert"
|
||||||
{ value: 2, label: $t('userRole.2'), color: '--green' }, // Light green for "Verifiziert"
|
{ value: 2, label: $t('userRole.2'), color: '--green' }, // Light green for "Verifiziert"
|
||||||
@@ -60,24 +70,26 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const TABS = hasPrivilige(user, PERMISSIONS.Member)
|
/** @type { (keyof user)[] } */
|
||||||
? ['profile', 'licence', 'membership', 'bankaccount']
|
const TABS = ['membership', 'licence', 'bank_account'];
|
||||||
: ['profile', 'bankaccount', 'membership'];
|
|
||||||
let activeTab = TABS[0];
|
let activeTab = 'profile';
|
||||||
|
|
||||||
let isUpdating = false,
|
let isUpdating = false,
|
||||||
password = '',
|
password = '',
|
||||||
confirm_password = '';
|
confirm_password = '';
|
||||||
|
|
||||||
/** @type {Object.<string, App.Locals['licence_categories']>} */
|
/** @type {Object.<string, App.Locals['licence_categories']>} */
|
||||||
$: groupedCategories = groupCategories(licence_categories);
|
$: groupedCategories = licence_categories ? groupCategories(licence_categories) : {};
|
||||||
$: subscriptionModelOptions = subscriptions.map((sub) => ({
|
$: subscriptionOptions = subscriptions
|
||||||
|
? subscriptions.map((sub) => ({
|
||||||
value: sub?.name ?? '',
|
value: sub?.name ?? '',
|
||||||
label: sub?.name ?? ''
|
label: sub?.name ?? ''
|
||||||
}));
|
}))
|
||||||
$: selectedSubscriptionModel =
|
: [];
|
||||||
subscriptions.find((sub) => sub?.name === user.membership?.subscription_model.name) || null;
|
$: selectedSubscription = subscriptions
|
||||||
|
? subscriptions.find((sub) => sub?.name === user.membership?.subscription.name) || null
|
||||||
|
: null;
|
||||||
/**
|
/**
|
||||||
* creates groups of categories depending on the first letter
|
* creates groups of categories depending on the first letter
|
||||||
* @param {App.Locals['licence_categories']} categories - the categories to sort and group
|
* @param {App.Locals['licence_categories']} categories - the categories to sort and group
|
||||||
@@ -99,25 +111,25 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @type {import('@sveltejs/kit').SubmitFunction} */
|
||||||
* Sets the active tab
|
const handleUpdate = ({ cancel }) => {
|
||||||
* @param {string} tab - The tab to set as active
|
if (!submit_form) {
|
||||||
*/
|
cancel();
|
||||||
function setActiveTab(tab) {
|
dispatch('close');
|
||||||
activeTab = tab;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
|
|
||||||
const handleUpdate = async () => {
|
|
||||||
isUpdating = true;
|
isUpdating = true;
|
||||||
|
|
||||||
return async ({ result }) => {
|
return async ({ result }) => {
|
||||||
isUpdating = false;
|
isUpdating = false;
|
||||||
|
|
||||||
if (result.type === 'success' || result.type === 'redirect') {
|
if (result.type === 'success' || result.type === 'redirect') {
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
|
document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
await applyAction(result);
|
console.log('submitting');
|
||||||
|
return submit_form ? await applyAction(result) : undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -125,7 +137,18 @@
|
|||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<SmallLoader width={30} message={$t('loading.user_data')} />
|
<SmallLoader width={30} message={$t('loading.user_data')} />
|
||||||
{:else if user}
|
{:else if user}
|
||||||
<form class="content" action="?/updateUser" method="POST" use:enhance={handleUpdate}>
|
<form
|
||||||
|
class="content"
|
||||||
|
action="?/updateUser"
|
||||||
|
method="POST"
|
||||||
|
use:enhance={handleUpdate}
|
||||||
|
on:submit={(/** @type{SubmitEvent}*/ e) => {
|
||||||
|
if (!submit_form) {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch('close');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<input name="user[id]" type="hidden" bind:value={user.id} />
|
<input name="user[id]" type="hidden" bind:value={user.id} />
|
||||||
<h1 class="step-title" style="text-align: center;">
|
<h1 class="step-title" style="text-align: center;">
|
||||||
{user.id ? $t('user.edit') : $t('user.create')}
|
{user.id ? $t('user.edit') : $t('user.create')}
|
||||||
@@ -153,18 +176,29 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="button-container">
|
<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}
|
{#each TABS as tab}
|
||||||
|
{#if user[tab] != null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="button-dark"
|
class="button-dark"
|
||||||
class:active={activeTab === tab}
|
class:active={activeTab === tab}
|
||||||
on:click={() => setActiveTab(tab)}
|
on:click={() => (activeTab = tab)}
|
||||||
>
|
>
|
||||||
{$t(tab)}
|
{$t('user.' + tab)}
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" style="display: {activeTab === 'profile' ? 'block' : 'none'}">
|
<div class="tab-content" style="display: {activeTab === 'profile' ? 'block' : 'none'}">
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||||
<InputField
|
<InputField
|
||||||
name="user[status]"
|
name="user[status]"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -173,6 +207,7 @@
|
|||||||
options={userStatusOptions}
|
options={userStatusOptions}
|
||||||
readonly={readonlyUser}
|
readonly={readonlyUser}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
{#if hasPrivilige(editor, PERMISSIONS.Super)}
|
{#if hasPrivilige(editor, PERMISSIONS.Super)}
|
||||||
<InputField
|
<InputField
|
||||||
name="user[role_id]"
|
name="user[role_id]"
|
||||||
@@ -237,36 +272,30 @@
|
|||||||
bind:value={user.phone}
|
bind:value={user.phone}
|
||||||
placeholder={$t('placeholder.phone')}
|
placeholder={$t('placeholder.phone')}
|
||||||
/>
|
/>
|
||||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
|
||||||
<InputField
|
<InputField
|
||||||
name="user[dateofbirth]"
|
name="user[dateofbirth]"
|
||||||
type="date"
|
type="date"
|
||||||
label={$t('user.dateofbirth')}
|
label={$t('user.dateofbirth')}
|
||||||
bind:value={user.dateofbirth}
|
bind:value={user.dateofbirth}
|
||||||
placeholder={$t('placeholder.dateofbirth')}
|
placeholder={$t('placeholder.dateofbirth')}
|
||||||
required={true}
|
|
||||||
readonly={readonlyUser}
|
readonly={readonlyUser}
|
||||||
/>
|
/>
|
||||||
{/if}
|
|
||||||
<InputField
|
<InputField
|
||||||
name="user[address]"
|
name="user[address]"
|
||||||
label={$t('address')}
|
label={$t('address')}
|
||||||
bind:value={user.address}
|
bind:value={user.address}
|
||||||
required={true}
|
|
||||||
placeholder={$t('placeholder.address')}
|
placeholder={$t('placeholder.address')}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="user[zip_code]"
|
name="user[zip_code]"
|
||||||
label={$t('zip_code')}
|
label={$t('zip_code')}
|
||||||
bind:value={user.zip_code}
|
bind:value={user.zip_code}
|
||||||
required={true}
|
|
||||||
placeholder={$t('placeholder.zip_code')}
|
placeholder={$t('placeholder.zip_code')}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="user[city]"
|
name="user[city]"
|
||||||
label={$t('city')}
|
label={$t('city')}
|
||||||
bind:value={user.city}
|
bind:value={user.city}
|
||||||
required={true}
|
|
||||||
placeholder={$t('placeholder.city')}
|
placeholder={$t('placeholder.city')}
|
||||||
/>
|
/>
|
||||||
{#if !readonlyUser}
|
{#if !readonlyUser}
|
||||||
@@ -283,7 +312,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
{#if hasPrivilige(user, PERMISSIONS.Member) && user.licence}
|
||||||
<div class="tab-content" style="display: {activeTab === 'licence' ? 'block' : 'none'}">
|
<div class="tab-content" style="display: {activeTab === 'licence' ? 'block' : 'none'}">
|
||||||
<InputField
|
<InputField
|
||||||
name="user[licence][status]"
|
name="user[licence][status]"
|
||||||
@@ -354,7 +383,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="tab-content" style="display: {activeTab === 'membership' ? 'block' : 'none'}">
|
{#if user.membership}
|
||||||
|
<div
|
||||||
|
class="tab-content"
|
||||||
|
style="display: {activeTab === 'membership' && subscriptions ? 'block' : 'none'}"
|
||||||
|
>
|
||||||
<InputField
|
<InputField
|
||||||
name="user[membership][status]"
|
name="user[membership][status]"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -364,34 +397,34 @@
|
|||||||
readonly={readonlyUser}
|
readonly={readonlyUser}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="user[membership][subscription_model][name]"
|
name="user[membership][subscription][name]"
|
||||||
type="select"
|
type="select"
|
||||||
label={$t('subscription.subscription')}
|
label={$t('subscriptions.subscription')}
|
||||||
bind:value={user.membership.subscription_model.name}
|
bind:value={user.membership.subscription.name}
|
||||||
options={subscriptionModelOptions}
|
options={subscriptionOptions}
|
||||||
readonly={readonlyUser || !hasPrivilige(user, PERMISSIONS.Member)}
|
readonly={readonlyUser || !hasPrivilige(user, PERMISSIONS.Member)}
|
||||||
/>
|
/>
|
||||||
<div class="subscription-info">
|
<div class="subscription-info">
|
||||||
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
{#if hasPrivilige(user, PERMISSIONS.Member)}
|
||||||
<div class="subscription-column">
|
<div class="subscription-column">
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('subscription.monthly_fee')}:</strong>
|
<strong>{$t('subscriptions.monthly_fee')}:</strong>
|
||||||
{selectedSubscriptionModel?.monthly_fee || '-'} €
|
{selectedSubscription?.monthly_fee || '-'} €
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('subscription.hourly_rate')}:</strong>
|
<strong>{$t('subscriptions.hourly_rate')}:</strong>
|
||||||
{selectedSubscriptionModel?.hourly_rate || '-'} €
|
{selectedSubscription?.hourly_rate || '-'} €
|
||||||
</p>
|
</p>
|
||||||
{#if selectedSubscriptionModel?.included_hours_per_year}
|
{#if selectedSubscription?.included_hours_per_year}
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('subscription.included_hours_per_year')}:</strong>
|
<strong>{$t('subscriptions.included_hours_per_year')}:</strong>
|
||||||
{selectedSubscriptionModel?.included_hours_per_year}
|
{selectedSubscription?.included_hours_per_year}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if selectedSubscriptionModel?.included_hours_per_month}
|
{#if selectedSubscription?.included_hours_per_month}
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('subscription.included_hours_per_month')}:</strong>
|
<strong>{$t('subscriptions.included_hours_per_month')}:</strong>
|
||||||
{selectedSubscriptionModel?.included_hours_per_month}
|
{selectedSubscription?.included_hours_per_month}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -399,12 +432,12 @@
|
|||||||
<div class="subscription-column">
|
<div class="subscription-column">
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('details')}:</strong>
|
<strong>{$t('details')}:</strong>
|
||||||
{selectedSubscriptionModel?.details || '-'}
|
{selectedSubscription?.details || '-'}
|
||||||
</p>
|
</p>
|
||||||
{#if selectedSubscriptionModel?.conditions}
|
{#if selectedSubscription?.conditions}
|
||||||
<p>
|
<p>
|
||||||
<strong>{$t('subscription.conditions')}:</strong>
|
<strong>{$t('subscriptions.conditions')}:</strong>
|
||||||
{selectedSubscriptionModel?.conditions}
|
{selectedSubscription?.conditions}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -436,7 +469,9 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}">
|
{/if}
|
||||||
|
{#if user.bank_account}
|
||||||
|
<div class="tab-content" style="display: {activeTab === 'bank_account' ? 'block' : 'none'}">
|
||||||
<InputField
|
<InputField
|
||||||
name="user[bank_account][account_holder_name]"
|
name="user[bank_account][account_holder_name]"
|
||||||
label={$t('bank_account_holder')}
|
label={$t('bank_account_holder')}
|
||||||
@@ -454,7 +489,6 @@
|
|||||||
label={$t('iban')}
|
label={$t('iban')}
|
||||||
bind:value={user.bank_account.iban}
|
bind:value={user.bank_account.iban}
|
||||||
placeholder={$t('placeholder.iban')}
|
placeholder={$t('placeholder.iban')}
|
||||||
required={true}
|
|
||||||
toUpperCase={true}
|
toUpperCase={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
@@ -479,6 +513,7 @@
|
|||||||
readonly={true}
|
readonly={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#if isUpdating}
|
{#if isUpdating}
|
||||||
<SmallLoader width={30} message={$t('loading.updating')} />
|
<SmallLoader width={30} message={$t('loading.updating')} />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export default {
|
|||||||
5: 'Passiv'
|
5: 'Passiv'
|
||||||
},
|
},
|
||||||
userRole: {
|
userRole: {
|
||||||
|
'-1': 'Unfallgegner',
|
||||||
0: 'Sponsor',
|
0: 'Sponsor',
|
||||||
1: 'Mitglied',
|
1: 'Mitglied',
|
||||||
2: 'Betrachter',
|
2: 'Betrachter',
|
||||||
@@ -14,6 +15,12 @@ export default {
|
|||||||
8: 'Administrator'
|
8: 'Administrator'
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
|
car_name: 'Hat das Fahrzeug einen Namen?',
|
||||||
|
car_brand: 'Fahrzeughersteller eingeben...',
|
||||||
|
car_model: 'Fahrzeugmodell eingeben...',
|
||||||
|
car_color: 'Fahrzeugfarbe eingeben...',
|
||||||
|
car_licence_plate: 'Fahrzeugkennzeichen eingeben...',
|
||||||
|
insurance_reference: 'Versicherungsnummer eingeben...',
|
||||||
password: 'Passwort eingeben...',
|
password: 'Passwort eingeben...',
|
||||||
email: 'Emailadresse eingeben...',
|
email: 'Emailadresse eingeben...',
|
||||||
company: 'Firmennamen eingeben...',
|
company: 'Firmennamen eingeben...',
|
||||||
@@ -69,12 +76,13 @@ export default {
|
|||||||
validation: {
|
validation: {
|
||||||
invalid: 'ungültig',
|
invalid: 'ungültig',
|
||||||
invalid_user_id: 'Nutzer ID ungültig',
|
invalid_user_id: 'Nutzer ID ungültig',
|
||||||
invalid_subscription_model: 'Model nicht gefunden',
|
invalid_subscription: 'Model nicht gefunden',
|
||||||
user_not_found: '{field} konnte nicht gefunden werden',
|
user_not_found: '{field} konnte nicht gefunden werden',
|
||||||
invalid_user_data: 'Nutzerdaten ungültig',
|
invalid_user_data: 'Nutzerdaten ungültig',
|
||||||
user_not_found_or_wrong_password: 'Existiert nicht oder falsches Passwort',
|
user_not_found_or_wrong_password: 'Existiert nicht oder falsches Passwort',
|
||||||
email_already_registered: 'Ein Mitglied wurde schon mit dieser Emailadresse erstellt.',
|
email_already_registered: 'Ein Mitglied wurde schon mit dieser Emailadresse erstellt.',
|
||||||
password_already_changed: 'Das Passwort wurde schon geändert.',
|
password_already_changed: 'Das Passwort wurde schon geändert.',
|
||||||
|
user_already_verified: 'Ihre Email Adresse wurde schon bestätigt.',
|
||||||
insecure: 'Unsicheres Passwort, versuchen Sie {message}',
|
insecure: 'Unsicheres Passwort, versuchen Sie {message}',
|
||||||
longer: 'oder verwenden Sie ein längeres Passwort',
|
longer: 'oder verwenden Sie ein längeres Passwort',
|
||||||
special: 'mehr Sonderzeichen einzufügen',
|
special: 'mehr Sonderzeichen einzufügen',
|
||||||
@@ -123,6 +131,7 @@ export default {
|
|||||||
edit: 'Nutzer bearbeiten',
|
edit: 'Nutzer bearbeiten',
|
||||||
create: 'Nutzer erstellen',
|
create: 'Nutzer erstellen',
|
||||||
user: 'Nutzer',
|
user: 'Nutzer',
|
||||||
|
member: 'Mitglied',
|
||||||
management: 'Mitgliederverwaltung',
|
management: 'Mitgliederverwaltung',
|
||||||
id: 'Mitgliedsnr',
|
id: 'Mitgliedsnr',
|
||||||
first_name: 'Vorname',
|
first_name: 'Vorname',
|
||||||
@@ -130,11 +139,14 @@ export default {
|
|||||||
phone: 'Telefonnummer',
|
phone: 'Telefonnummer',
|
||||||
dateofbirth: 'Geburtstag',
|
dateofbirth: 'Geburtstag',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
|
membership: 'Mitgliedschaft',
|
||||||
|
bank_account: 'Kontodaten',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
role: 'Nutzerrolle',
|
role: 'Nutzerrolle',
|
||||||
supporter: 'Sponsor'
|
supporter: 'Sponsor',
|
||||||
|
opponent: 'Unfallgegner'
|
||||||
},
|
},
|
||||||
subscription: {
|
subscriptions: {
|
||||||
name: 'Modellname',
|
name: 'Modellname',
|
||||||
edit: 'Modell bearbeiten',
|
edit: 'Modell bearbeiten',
|
||||||
create: 'Modell erstellen',
|
create: 'Modell erstellen',
|
||||||
@@ -146,30 +158,63 @@ export default {
|
|||||||
included_hours_per_year: 'Inkludierte Stunden pro Jahr',
|
included_hours_per_year: 'Inkludierte Stunden pro Jahr',
|
||||||
included_hours_per_month: 'Inkludierte Stunden pro Monat'
|
included_hours_per_month: 'Inkludierte Stunden pro Monat'
|
||||||
},
|
},
|
||||||
|
car: {
|
||||||
|
car: 'Fahrzeug',
|
||||||
|
model: 'Modell',
|
||||||
|
brand: 'Marke',
|
||||||
|
licence_plate: 'Kennzeichen',
|
||||||
|
edit: 'Fahrzeug bearbeiten',
|
||||||
|
create: 'Fahrzeug hinzufügen',
|
||||||
|
damages: 'Schäden',
|
||||||
|
start_date: 'Anschaffungsdatum',
|
||||||
|
end_date: 'Leasingende',
|
||||||
|
leasing_rate: 'Leasingrate'
|
||||||
|
},
|
||||||
|
insurances: {
|
||||||
|
edit: 'Daten bearbeiten',
|
||||||
|
create: 'Versicherung erstellen'
|
||||||
|
},
|
||||||
loading: {
|
loading: {
|
||||||
user_data: 'Lade Nutzerdaten',
|
user_data: 'Lade Nutzerdaten',
|
||||||
subscription_data: 'Lade Modelldaten',
|
subscription_data: 'Lade Modelldaten',
|
||||||
|
insurance_data: 'Lade Versicherungsdaten',
|
||||||
|
car_data: 'Lade Fahrzeugdaten',
|
||||||
please_wait: 'Bitte warten...',
|
please_wait: 'Bitte warten...',
|
||||||
updating: 'Aktualisiere...'
|
updating: 'Aktualisiere...'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',
|
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',
|
||||||
subscription_deletion: 'Soll das Tarifmodell {name} wirklich gelöscht werden?'
|
subscription_deletion: 'Soll das Tarifmodell {name} wirklich gelöscht werden?',
|
||||||
|
car_deletion: 'Soll das Fahrzeug {name} wirklich gelöscht werden?',
|
||||||
|
insurance_deletion: 'Soll die Versicherung {name} wirklich gelöscht werden?',
|
||||||
|
damage_deletion: 'Soll der Schaden {name} wirklich gelöscht werden?',
|
||||||
|
backend_access: 'Soll {firstname} {lastname} Backend Zugriff gewährt werden?'
|
||||||
},
|
},
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
confirm: 'Bestätigen',
|
confirm: 'Bestätigen',
|
||||||
actions: 'Aktionen',
|
actions: 'Aktionen',
|
||||||
|
create: 'Hinzufügen',
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
delete: 'Löschen',
|
delete: 'Löschen',
|
||||||
|
not_set: 'Nicht gesetzt',
|
||||||
|
noone: 'Niemand',
|
||||||
search: 'Suche:',
|
search: 'Suche:',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
date: 'Datum',
|
||||||
|
price: 'Preis',
|
||||||
|
color: 'Farbe',
|
||||||
|
grant_backend_access: 'Backend Zugriff gewähren',
|
||||||
|
no_insurance: 'Keine Versicherung',
|
||||||
supporter: 'Sponsoren',
|
supporter: 'Sponsoren',
|
||||||
mandate_date_signed: 'Mandatserteilungsdatum',
|
mandate_date_signed: 'Mandatserteilungsdatum',
|
||||||
licence_categories: 'Führerscheinklassen',
|
licence_categories: 'Führerscheinklassen',
|
||||||
subscription_model: 'Mitgliedschatfsmodell',
|
subscription: 'Mitgliedschatfsmodell',
|
||||||
licence: 'Führerschein',
|
licence: 'Führerschein',
|
||||||
licence_number: 'Führerscheinnummer',
|
licence_number: 'Führerscheinnummer',
|
||||||
|
insurance: 'Versicherung',
|
||||||
|
insurance_reference: 'Versicherungsnummer',
|
||||||
issued_date: 'Ausgabedatum',
|
issued_date: 'Ausgabedatum',
|
||||||
|
month: 'Monat',
|
||||||
expiration_date: 'Ablaufdatum',
|
expiration_date: 'Ablaufdatum',
|
||||||
country: 'Land',
|
country: 'Land',
|
||||||
details: 'Details',
|
details: 'Details',
|
||||||
@@ -188,8 +233,7 @@ export default {
|
|||||||
company: 'Firma',
|
company: 'Firma',
|
||||||
login: 'Anmeldung',
|
login: 'Anmeldung',
|
||||||
profile: 'Profil',
|
profile: 'Profil',
|
||||||
membership: 'Mitgliedschaft',
|
cars: 'Fahrzeuge',
|
||||||
bankaccount: 'Kontodaten',
|
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
start: 'Beginn',
|
start: 'Beginn',
|
||||||
end: 'Ende',
|
end: 'Ende',
|
||||||
@@ -202,6 +246,7 @@ export default {
|
|||||||
payments: 'Zahlungen',
|
payments: 'Zahlungen',
|
||||||
add_new: 'Neu',
|
add_new: 'Neu',
|
||||||
email_sent: 'Email wurde gesendet..',
|
email_sent: 'Email wurde gesendet..',
|
||||||
|
verification: 'Verifikation',
|
||||||
// For payments section
|
// For payments section
|
||||||
payment: {
|
payment: {
|
||||||
id: 'Zahlungs-Nr',
|
id: 'Zahlungs-Nr',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default {
|
|||||||
bic: 'Enter BIC (for non-German accounts)...',
|
bic: 'Enter BIC (for non-German accounts)...',
|
||||||
mandate_reference: 'Enter SEPA mandate reference...',
|
mandate_reference: 'Enter SEPA mandate reference...',
|
||||||
notes: 'Your notes about {name}...',
|
notes: 'Your notes about {name}...',
|
||||||
licence_number: 'On the driver’s license under field 5',
|
licence_number: 'On the driver’s licence under field 5',
|
||||||
issued_date: 'Issue date under field 4a',
|
issued_date: 'Issue date under field 4a',
|
||||||
expiration_date: 'Expiration date under field 4b',
|
expiration_date: 'Expiration date under field 4b',
|
||||||
issuing_country: 'Issuing country',
|
issuing_country: 'Issuing country',
|
||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
validation: {
|
validation: {
|
||||||
invalid: 'Invalid',
|
invalid: 'Invalid',
|
||||||
invalid_user_id: 'Invalid user ID',
|
invalid_user_id: 'Invalid user ID',
|
||||||
invalid_subscription_model: 'Model not found',
|
invalid_subscription: 'Model not found',
|
||||||
user_not_found: '{field} could not be found',
|
user_not_found: '{field} could not be found',
|
||||||
invalid_user_data: 'Invalid user data',
|
invalid_user_data: 'Invalid user data',
|
||||||
user_not_found_or_wrong_password: 'Does not exist or wrong password',
|
user_not_found_or_wrong_password: 'Does not exist or wrong password',
|
||||||
@@ -81,7 +81,7 @@ export default {
|
|||||||
bic: 'Invalid. Format: BELADEBEXXX',
|
bic: 'Invalid. Format: BELADEBEXXX',
|
||||||
email: 'Invalid format',
|
email: 'Invalid format',
|
||||||
number: 'Is not a number',
|
number: 'Is not a number',
|
||||||
euDriversLicence: 'Is not a European driver’s license',
|
euDriversLicence: 'Is not a European driver’s licence',
|
||||||
lte: 'Is too large/new',
|
lte: 'Is too large/new',
|
||||||
gt: 'Is too small/old',
|
gt: 'Is too small/old',
|
||||||
required: 'Field is required',
|
required: 'Field is required',
|
||||||
@@ -128,7 +128,7 @@ export default {
|
|||||||
role: 'User Role',
|
role: 'User Role',
|
||||||
supporter: 'Sponsor'
|
supporter: 'Sponsor'
|
||||||
},
|
},
|
||||||
subscription: {
|
subscriptions: {
|
||||||
name: 'Model Name',
|
name: 'Model Name',
|
||||||
edit: 'Edit Model',
|
edit: 'Edit Model',
|
||||||
create: 'Create Model',
|
create: 'Create Model',
|
||||||
@@ -159,10 +159,10 @@ export default {
|
|||||||
name: 'Name',
|
name: 'Name',
|
||||||
supporter: 'Sponsors',
|
supporter: 'Sponsors',
|
||||||
mandate_date_signed: 'Mandate Signing Date',
|
mandate_date_signed: 'Mandate Signing Date',
|
||||||
licence_categories: 'Driver’s License Categories',
|
licence_categories: 'Driver’s licence Categories',
|
||||||
subscription_model: 'Membership Model',
|
subscription: 'Membership Model',
|
||||||
licence: 'Driver’s License',
|
licence: 'Driver’s licence',
|
||||||
licence_number: 'Driver’s License Number',
|
licence_number: 'Driver’s licence Number',
|
||||||
issued_date: 'Issue Date',
|
issued_date: 'Issue Date',
|
||||||
expiration_date: 'Expiration Date',
|
expiration_date: 'Expiration Date',
|
||||||
country: 'Country',
|
country: 'Country',
|
||||||
@@ -182,7 +182,7 @@ export default {
|
|||||||
login: 'Login',
|
login: 'Login',
|
||||||
profile: 'Profile',
|
profile: 'Profile',
|
||||||
membership: 'Membership',
|
membership: 'Membership',
|
||||||
bankaccount: 'Bank Account',
|
bank_account: 'Bank Account',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
|
|||||||
@@ -10,5 +10,3 @@ export const PERMISSIONS = {
|
|||||||
Delete: 4,
|
Delete: 4,
|
||||||
Super: 8
|
Super: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SUPPORTER_SUBSCRIPTION_MODEL_NAME = 'Keins';
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// src/lib/utils/defaults.js
|
// src/lib/utils/defaults.js
|
||||||
|
|
||||||
import { SUPPORTER_SUBSCRIPTION_MODEL_NAME } from './constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {App.Types['subscription']}
|
* @returns {App.Types['subscription']}
|
||||||
*/
|
*/
|
||||||
@@ -28,7 +26,7 @@ export function defaultMembership() {
|
|||||||
start_date: '',
|
start_date: '',
|
||||||
end_date: '',
|
end_date: '',
|
||||||
parent_member_id: 0,
|
parent_member_id: 0,
|
||||||
subscription_model: defaultSubscription()
|
subscription: defaultSubscription()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +77,6 @@ export function defaultUser() {
|
|||||||
company: '',
|
company: '',
|
||||||
dateofbirth: '',
|
dateofbirth: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
profile_picture: '',
|
|
||||||
payment_status: 0,
|
|
||||||
status: 1,
|
status: 1,
|
||||||
role_id: 1,
|
role_id: 1,
|
||||||
membership: defaultMembership(),
|
membership: defaultMembership(),
|
||||||
@@ -93,27 +89,83 @@ export function defaultUser() {
|
|||||||
* @returns {App.Locals['user']}
|
* @returns {App.Locals['user']}
|
||||||
*/
|
*/
|
||||||
export function defaultSupporter() {
|
export function defaultSupporter() {
|
||||||
let supporter = {
|
let supporter = defaultUser();
|
||||||
id: 0,
|
supporter.status = 5;
|
||||||
email: '',
|
supporter.role_id = 0;
|
||||||
first_name: '',
|
supporter.licence = null;
|
||||||
last_name: '',
|
supporter.membership = null;
|
||||||
password: '',
|
|
||||||
phone: '',
|
|
||||||
address: '',
|
|
||||||
zip_code: '',
|
|
||||||
city: '',
|
|
||||||
company: '',
|
|
||||||
dateofbirth: '',
|
|
||||||
notes: '',
|
|
||||||
profile_picture: '',
|
|
||||||
payment_status: 0,
|
|
||||||
status: 1,
|
|
||||||
role_id: 0,
|
|
||||||
membership: defaultMembership(),
|
|
||||||
licence: defaultLicence(),
|
|
||||||
bank_account: defaultBankAccount()
|
|
||||||
};
|
|
||||||
supporter.membership.subscription_model.name = SUPPORTER_SUBSCRIPTION_MODEL_NAME;
|
|
||||||
return supporter;
|
return supporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {App.Locals['user']}
|
||||||
|
*/
|
||||||
|
export function defaultOpponent() {
|
||||||
|
let opponent = defaultUser();
|
||||||
|
opponent.status = 5;
|
||||||
|
opponent.role_id = -1;
|
||||||
|
opponent.licence = null;
|
||||||
|
opponent.membership = null;
|
||||||
|
return opponent;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {App.Types['location']}
|
||||||
|
*/
|
||||||
|
export function defaultLocation() {
|
||||||
|
return {
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {App.Types['damage']}
|
||||||
|
*/
|
||||||
|
export function defaultDamage() {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
opponent: defaultOpponent(),
|
||||||
|
driver_id: -1,
|
||||||
|
insurance: defaultInsurance(),
|
||||||
|
date: '',
|
||||||
|
notes: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {App.Types['insurance']}
|
||||||
|
*/
|
||||||
|
export function defaultInsurance() {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
company: '',
|
||||||
|
reference: '',
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
notes: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {App.Types['car']}
|
||||||
|
*/
|
||||||
|
export function defaultCar() {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
status: 0,
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
price: 0,
|
||||||
|
rate: 0,
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
color: '',
|
||||||
|
licence_plate: '',
|
||||||
|
location: defaultLocation(),
|
||||||
|
damages: [],
|
||||||
|
insurances: [],
|
||||||
|
notes: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function isEmpty(obj) {
|
|||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
export function toRFC3339(dateString) {
|
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);
|
const date = new Date(dateString);
|
||||||
return date.toISOString();
|
return date.toISOString();
|
||||||
}
|
}
|
||||||
@@ -88,6 +88,31 @@ export function fromRFC3339(dateString) {
|
|||||||
return date.toISOString().split('T')[0];
|
return date.toISOString().split('T')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {App.Types['car']} car - The car object to format
|
||||||
|
*/
|
||||||
|
export function carDatesFromRFC3339(car) {
|
||||||
|
car.end_date = fromRFC3339(car.end_date);
|
||||||
|
car.start_date = fromRFC3339(car.start_date);
|
||||||
|
car.insurances?.forEach((insurance) => {
|
||||||
|
insurance.start_date = fromRFC3339(insurance.start_date);
|
||||||
|
insurance.end_date = fromRFC3339(insurance.end_date);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {App.Types['car']} car - The car object to format
|
||||||
|
*/
|
||||||
|
export function carDatesToRFC3339(car) {
|
||||||
|
car.end_date = toRFC3339(car.end_date);
|
||||||
|
car.start_date = toRFC3339(car.start_date);
|
||||||
|
car.insurances?.forEach((insurance) => {
|
||||||
|
insurance.start_date = toRFC3339(insurance.start_date);
|
||||||
|
insurance.end_date = toRFC3339(insurance.end_date);
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {App.Locals['user']} user - The user object to format
|
* @param {App.Locals['user']} user - The user object to format
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
import { defaultBankAccount, defaultMembership } from './defaults';
|
||||||
import { toRFC3339 } from './helpers';
|
import { toRFC3339 } from './helpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts FormData to a nested object structure
|
* Converts FormData to a nested object structure
|
||||||
* @param {FormData} formData - The FormData object to convert
|
* @param {FormData} formData - The FormData object to convert
|
||||||
* @returns {{ object: Partial<App.Locals['user']> | Partial<App.Types['subscription']>, confirm_password: string }} Nested object representation of the form data
|
* @returns {{ object: Partial<App.Locals['user']> | Partial<App.Types['subscription']> | Partial<App.Types['car']>, confirm_password: string }} Nested object representation of the form data
|
||||||
*/
|
*/
|
||||||
export function formDataToObject(formData) {
|
export function formDataToObject(formData) {
|
||||||
/** @type { Partial<App.Locals['user']> | Partial<App.Types['subscription']> } */
|
/** @type { Partial<App.Locals['user']> | Partial<App.Types['subscription']> | Partial<App.Types['car']> } */
|
||||||
const object = {};
|
const object = {};
|
||||||
let confirm_password = '';
|
let confirm_password = '';
|
||||||
|
|
||||||
@@ -24,20 +25,18 @@ export function formDataToObject(formData) {
|
|||||||
|
|
||||||
// console.log('Current object state:', JSON.stringify(current));
|
// console.log('Current object state:', JSON.stringify(current));
|
||||||
for (let i = 0; i < keys.length - 1; i++) {
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
/**
|
const currentKey = keys[i];
|
||||||
* Create nested object if it doesn't exist
|
const nextKey = keys[i + 1];
|
||||||
* @type {Record<string, any>}
|
const isNextKeyArrayIndex = !isNaN(Number(nextKey));
|
||||||
* @description Ensures proper nesting structure for user data fields
|
if (!current[currentKey]) {
|
||||||
* @example
|
// If next key is a number, initialize an array, otherwise an object
|
||||||
* // For input name="user[membership][status]"
|
current[currentKey] = isNextKeyArrayIndex ? [] : {};
|
||||||
* // Creates: { user: { membership: { status: value } } }
|
}
|
||||||
*/
|
|
||||||
current[keys[i]] = current[keys[i]] || {};
|
|
||||||
/**
|
/**
|
||||||
* Move to the next level of the object
|
* Move to the next level of the object
|
||||||
* @type {Record<string, any>}
|
* @type {Record<string, any>}
|
||||||
*/
|
*/
|
||||||
current = current[keys[i]];
|
current = current[currentKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastKey = keys[keys.length - 1];
|
const lastKey = keys[keys.length - 1];
|
||||||
@@ -49,91 +48,108 @@ export function formDataToObject(formData) {
|
|||||||
} catch {
|
} catch {
|
||||||
current[lastKey].push(value);
|
current[lastKey].push(value);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
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 {
|
} else {
|
||||||
current[lastKey] = value;
|
current[lastKey] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { object: object, confirm_password: confirm_password };
|
return { object: object, confirm_password: confirm_password };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the raw form data into the expected user data structure
|
* Processes the raw form data into the expected membership data structure
|
||||||
* @param {{ object: Partial<App.Locals['user']>, confirm_password: string} } rawData - The raw form data object
|
* @param { App.Types['membership'] } membership - The raw form data object
|
||||||
* @returns {{ user: Partial<App.Locals['user']> }} Processed user data
|
* @returns {App.Types['membership']} Processed membership data
|
||||||
*/
|
*/
|
||||||
export function processUserFormData(rawData) {
|
export function processMembershipFormData(membership) {
|
||||||
/** @type {{ user: Partial<App.Locals['user']> }} */
|
return {
|
||||||
let processedData = {
|
id: Number(membership.id) || 0,
|
||||||
user: {
|
status: Number(membership.status),
|
||||||
id: Number(rawData.object.id) || 0,
|
start_date: toRFC3339(String(membership.start_date || '')),
|
||||||
status: Number(rawData.object.status),
|
end_date: toRFC3339(String(membership.end_date || '')),
|
||||||
role_id: Number(rawData.object.role_id),
|
parent_member_id: Number(membership.parent_member_id) || 0,
|
||||||
first_name: String(rawData.object.first_name),
|
subscription: processSubscriptionFormData(membership.subscription)
|
||||||
last_name: String(rawData.object.last_name),
|
};
|
||||||
email: String(rawData.object.email),
|
}
|
||||||
phone: String(rawData.object.phone || ''),
|
|
||||||
company: String(rawData.object.company || ''),
|
/**
|
||||||
dateofbirth: toRFC3339(String(rawData.object.dateofbirth || '')),
|
* Processes the raw form data into the expected licence data structure
|
||||||
address: String(rawData.object.address || ''),
|
* @param { App.Types['licence'] } licence - The raw form data object
|
||||||
zip_code: String(rawData.object.zip_code || ''),
|
* @returns {App.Types['licence']} Processed licence data
|
||||||
city: String(rawData.object.city || ''),
|
*/
|
||||||
notes: String(rawData.object.notes || ''),
|
export function processLicenceFormData(licence) {
|
||||||
profile_picture: String(rawData.object.profile_picture || ''),
|
return {
|
||||||
|
id: Number(licence?.id) || 0,
|
||||||
membership: {
|
status: Number(licence?.status),
|
||||||
id: Number(rawData.object.membership?.id) || 0,
|
number: String(licence?.number || ''),
|
||||||
status: Number(rawData.object.membership?.status),
|
issued_date: toRFC3339(String(licence?.issued_date || '')),
|
||||||
start_date: toRFC3339(String(rawData.object.membership?.start_date || '')),
|
expiration_date: toRFC3339(String(licence?.expiration_date || '')),
|
||||||
end_date: toRFC3339(String(rawData.object.membership?.end_date || '')),
|
country: String(licence?.country || ''),
|
||||||
parent_member_id: Number(rawData.object.membership?.parent_member_id) || 0,
|
categories: licence?.categories || []
|
||||||
subscription_model: {
|
};
|
||||||
id: Number(rawData.object.membership?.subscription_model?.id) || 0,
|
}
|
||||||
name: String(rawData.object.membership?.subscription_model?.name) || '',
|
|
||||||
details: String(rawData.object.membership?.subscription_model?.details) || '',
|
/**
|
||||||
conditions: String(rawData.object.membership?.subscription_model?.conditions) || '',
|
* Processes the raw form data into the expected bank_account data structure
|
||||||
hourly_rate: Number(rawData.object.membership?.subscription_model?.hourly_rate) || 0,
|
* @param { App.Types['bankAccount'] } bank_account - The raw form data object
|
||||||
monthly_fee: Number(rawData.object.membership?.subscription_model?.monthly_fee) || 0,
|
* @returns {App.Types['bankAccount']} Processed bank_account data
|
||||||
included_hours_per_month:
|
*/
|
||||||
Number(rawData.object.membership?.subscription_model?.included_hours_per_month) || 0,
|
export function processBankAccountFormData(bank_account) {
|
||||||
included_hours_per_year:
|
{
|
||||||
Number(rawData.object.membership?.subscription_model?.included_hours_per_year) || 0
|
return {
|
||||||
}
|
id: Number(bank_account?.id) || 0,
|
||||||
},
|
account_holder_name: String(bank_account?.account_holder_name || ''),
|
||||||
|
bank: String(bank_account?.bank || ''),
|
||||||
licence: {
|
iban: String(bank_account?.iban || ''),
|
||||||
id: Number(rawData.object.licence?.id) || 0,
|
bic: String(bank_account?.bic || ''),
|
||||||
status: Number(rawData.object.licence?.status),
|
mandate_reference: String(bank_account?.mandate_reference || ''),
|
||||||
number: String(rawData.object.licence?.number || ''),
|
mandate_date_signed: toRFC3339(String(bank_account?.mandate_date_signed || ''))
|
||||||
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 || []
|
|
||||||
},
|
|
||||||
|
|
||||||
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 || '')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
||||||
value !== null && value !== '' ? value : undefined
|
value !== null && value !== '' ? value : undefined
|
||||||
);
|
);
|
||||||
@@ -142,23 +158,21 @@ export function processUserFormData(rawData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the raw form data into the expected user data structure
|
* Processes the raw form data into the expected subscription data structure
|
||||||
* @param {{ object: Partial<App.Types['subscription']>} } rawData - The raw form data object
|
* @param {Partial<App.Types['subscription']>} subscription - The raw form data object
|
||||||
* @returns {{ subscription: Partial<App.Types['subscription']> }} Processed user data
|
* @returns {App.Types['subscription']} Processed user data
|
||||||
*/
|
*/
|
||||||
export function processSubscriptionFormData(rawData) {
|
export function processSubscriptionFormData(subscription) {
|
||||||
/** @type {{ subscription: Partial<App.Types['subscription']> }} */
|
/** @type {Partial<App.Types['subscription']>} */
|
||||||
let processedData = {
|
let processedData = {
|
||||||
subscription: {
|
id: Number(subscription.id) || 0,
|
||||||
id: Number(rawData.object.id) || 0,
|
name: String(subscription.name) || '',
|
||||||
name: String(rawData.object.name) || '',
|
details: String(subscription.details) || '',
|
||||||
details: String(rawData.object.details) || '',
|
conditions: String(subscription.conditions) || '',
|
||||||
conditions: String(rawData.object.conditions) || '',
|
hourly_rate: Number(subscription.hourly_rate) || 0,
|
||||||
hourly_rate: Number(rawData.object.hourly_rate) || 0,
|
monthly_fee: Number(subscription.monthly_fee) || 0,
|
||||||
monthly_fee: Number(rawData.object.monthly_fee) || 0,
|
included_hours_per_month: Number(subscription.included_hours_per_month) || 0,
|
||||||
included_hours_per_month: Number(rawData.object.included_hours_per_month) || 0,
|
included_hours_per_year: Number(subscription.included_hours_per_year) || 0
|
||||||
included_hours_per_year: Number(rawData.object.included_hours_per_year) || 0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
const clean = JSON.parse(JSON.stringify(processedData), (key, value) =>
|
||||||
value !== null && value !== '' ? value : undefined
|
value !== null && value !== '' ? value : undefined
|
||||||
@@ -166,3 +180,85 @@ export function processSubscriptionFormData(rawData) {
|
|||||||
console.dir(clean);
|
console.dir(clean);
|
||||||
return 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 {Partial<App.Types['car']>} car - The raw form data object
|
||||||
|
* @returns {App.Types['car']} Processed user data
|
||||||
|
*/
|
||||||
|
export function processCarFormData(car) {
|
||||||
|
console.dir(car);
|
||||||
|
/** @type {App.Types['car']} */
|
||||||
|
let processedData = {
|
||||||
|
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 }) => {
|
updateUser: async ({ request, fetch, cookies, locals }) => {
|
||||||
let formData = await request.formData();
|
let formData = await request.formData();
|
||||||
|
|
||||||
const rawData = formDataToObject(formData);
|
const rawFormData = formDataToObject(formData);
|
||||||
const processedData = processUserFormData(rawData);
|
/** @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;
|
// const isCreating = !processedData.user.id || processedData.user.id === 0;
|
||||||
// console.log('Is creating: ', isCreating);
|
// console.log('Is creating: ', isCreating);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
export async function load({ data }) {
|
export async function load({ data }) {
|
||||||
return {
|
return {
|
||||||
users: data.users,
|
users: data.users,
|
||||||
user: data.user
|
user: data.user,
|
||||||
|
cars: data.cars
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,63 @@
|
|||||||
import { BASE_API_URI } from '$lib/utils/constants';
|
import { BASE_API_URI } from '$lib/utils/constants';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { userDatesFromRFC3339, refreshCookie } from '$lib/utils/helpers';
|
import { userDatesFromRFC3339, refreshCookie, carDatesFromRFC3339 } from '$lib/utils/helpers';
|
||||||
import { base } from '$app/paths';
|
import { base } from '$app/paths';
|
||||||
|
|
||||||
/** @type {import('./$types').LayoutServerLoad} */
|
/** @type {import('./$types').LayoutServerLoad} */
|
||||||
export async function load({ cookies, fetch, locals }) {
|
export async function load({ cookies, fetch, locals }) {
|
||||||
const jwt = cookies.get('jwt');
|
const jwt = cookies.get('jwt');
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${BASE_API_URI}/auth/users/`, {
|
const [usersResponse, carsResponse] = await Promise.all([
|
||||||
|
fetch(`${BASE_API_URI}/auth/users`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: { Cookie: `jwt=${jwt}` }
|
||||||
Cookie: `jwt=${jwt}`
|
}),
|
||||||
}
|
fetch(`${BASE_API_URI}/auth/cars`, {
|
||||||
});
|
credentials: 'include',
|
||||||
if (!response.ok) {
|
headers: { Cookie: `jwt=${jwt}` }
|
||||||
// Clear the invalid JWT cookie
|
})
|
||||||
|
]);
|
||||||
|
if (!usersResponse.ok || !carsResponse.ok) {
|
||||||
cookies.delete('jwt', { path: '/' });
|
cookies.delete('jwt', { path: '/' });
|
||||||
throw redirect(302, `${base}/auth/login?next=${base}/auth/admin/users/`);
|
throw redirect(302, `${base}/auth/login?next=${base}/auth/admin/users/`);
|
||||||
}
|
}
|
||||||
|
const [usersData, carsData] = await Promise.all([usersResponse.json(), carsResponse.json()]);
|
||||||
|
// const response = await fetch(`${BASE_API_URI}/auth/users/`, {
|
||||||
|
// credentials: 'include',
|
||||||
|
// headers: {
|
||||||
|
// Cookie: `jwt=${jwt}`
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// if (!response.ok) {
|
||||||
|
// // Clear the invalid JWT cookie
|
||||||
|
// cookies.delete('jwt', { path: '/' });
|
||||||
|
// throw redirect(302, `${base}/auth/login?next=${base}/auth/admin/users/`);
|
||||||
|
// }
|
||||||
|
|
||||||
const data = await response.json();
|
// const data = await response.json();
|
||||||
/** @type {App.Locals['users']}*/
|
/** @type {App.Locals['users']}*/
|
||||||
const users = data.users;
|
const users = usersData.users;
|
||||||
|
/** @type {App.Types['car'][]} */
|
||||||
|
const cars = carsData.cars;
|
||||||
|
|
||||||
users.forEach((user) => {
|
users.forEach((user) => {
|
||||||
userDatesFromRFC3339(user);
|
userDatesFromRFC3339(user);
|
||||||
});
|
});
|
||||||
|
cars.forEach((car) => {
|
||||||
|
carDatesFromRFC3339(car);
|
||||||
|
});
|
||||||
|
|
||||||
locals.users = users;
|
locals.users = users;
|
||||||
|
locals.cars = cars;
|
||||||
// Check if the server sent a new token
|
// Check if the server sent a new token
|
||||||
const newToken = response.headers.get('Set-Cookie');
|
const newToken = usersResponse.headers.get('Set-Cookie');
|
||||||
refreshCookie(newToken, cookies);
|
refreshCookie(newToken, cookies);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscriptions: locals.subscriptions,
|
subscriptions: locals.subscriptions,
|
||||||
licence_categories: locals.licence_categories,
|
licence_categories: locals.licence_categories,
|
||||||
users: locals.users,
|
users: locals.users,
|
||||||
user: locals.user
|
user: locals.user,
|
||||||
|
cars: locals.cars
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching data:', error);
|
console.error('Error fetching data:', error);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { formatError, hasPrivilige, userDatesFromRFC3339 } from '$lib/utils/help
|
|||||||
import { fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import {
|
import {
|
||||||
formDataToObject,
|
formDataToObject,
|
||||||
|
processCarFormData,
|
||||||
processSubscriptionFormData,
|
processSubscriptionFormData,
|
||||||
processUserFormData
|
processUserFormData
|
||||||
} from '$lib/utils/processing';
|
} from '$lib/utils/processing';
|
||||||
@@ -36,11 +37,24 @@ export const actions = {
|
|||||||
updateUser: async ({ request, fetch, cookies, locals }) => {
|
updateUser: async ({ request, fetch, cookies, locals }) => {
|
||||||
let formData = await request.formData();
|
let formData = await request.formData();
|
||||||
|
|
||||||
const rawData = formDataToObject(formData);
|
const rawFormData = formDataToObject(formData);
|
||||||
const processedData = processUserFormData(rawData);
|
/** @type {{object: App.Locals['user'], confirm_password: string}} */
|
||||||
|
const rawData = {
|
||||||
|
object: /** @type {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 user = processUserFormData(rawData.object);
|
||||||
|
|
||||||
console.dir(processedData.user.membership);
|
console.dir(user.membership);
|
||||||
const isCreating = !processedData.user.id || processedData.user.id === 0;
|
const isCreating = !user.id || user.id === 0;
|
||||||
console.log('Is creating: ', isCreating);
|
console.log('Is creating: ', isCreating);
|
||||||
const apiURL = `${BASE_API_URI}/auth/users`;
|
const apiURL = `${BASE_API_URI}/auth/users`;
|
||||||
|
|
||||||
@@ -52,7 +66,7 @@ export const actions = {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Cookie: `jwt=${cookies.get('jwt')}`
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(processedData)
|
body: JSON.stringify(user)
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(apiURL, requestOptions);
|
const res = await fetch(apiURL, requestOptions);
|
||||||
@@ -81,10 +95,11 @@ export const actions = {
|
|||||||
updateSubscription: async ({ request, fetch, cookies }) => {
|
updateSubscription: async ({ request, fetch, cookies }) => {
|
||||||
let formData = await request.formData();
|
let formData = await request.formData();
|
||||||
|
|
||||||
const rawData = formDataToObject(formData);
|
const rawFormData = formDataToObject(formData);
|
||||||
const processedData = processSubscriptionFormData(rawData);
|
const rawSubscription = /** @type {Partial<App.Types['subscription']>} */ (rawFormData.object);
|
||||||
|
const subscription = processSubscriptionFormData(rawSubscription);
|
||||||
|
|
||||||
const isCreating = !processedData.subscription.id || processedData.subscription.id === 0;
|
const isCreating = !subscription.id || subscription.id === 0;
|
||||||
console.log('Is creating: ', isCreating);
|
console.log('Is creating: ', isCreating);
|
||||||
const apiURL = `${BASE_API_URI}/auth/subscriptions`;
|
const apiURL = `${BASE_API_URI}/auth/subscriptions`;
|
||||||
|
|
||||||
@@ -96,7 +111,7 @@ export const actions = {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Cookie: `jwt=${cookies.get('jwt')}`
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(processedData)
|
body: JSON.stringify(subscription)
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(apiURL, requestOptions);
|
const res = await fetch(apiURL, requestOptions);
|
||||||
@@ -112,6 +127,51 @@ export const actions = {
|
|||||||
throw redirect(303, `${base}/auth/admin/users`);
|
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 Error data or redirects user to the home page or the previous page
|
||||||
|
*/
|
||||||
|
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 isCreating = !car.id || car.id === 0;
|
||||||
|
console.log('Is creating: ', isCreating);
|
||||||
|
console.log('sending: ', JSON.stringify(car.damages));
|
||||||
|
|
||||||
|
const apiURL = `${BASE_API_URI}/auth/cars`;
|
||||||
|
|
||||||
|
/** @type {RequestInit} */
|
||||||
|
const requestOptions = {
|
||||||
|
method: isCreating ? 'POST' : 'PUT',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(car)
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
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`);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param request - The request object
|
* @param request - The request object
|
||||||
@@ -123,8 +183,9 @@ export const actions = {
|
|||||||
userDelete: async ({ request, fetch, cookies }) => {
|
userDelete: async ({ request, fetch, cookies }) => {
|
||||||
let formData = await request.formData();
|
let formData = await request.formData();
|
||||||
|
|
||||||
const rawData = formDataToObject(formData);
|
const rawFormData = formDataToObject(formData);
|
||||||
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`;
|
const apiURL = `${BASE_API_URI}/auth/users`;
|
||||||
|
|
||||||
@@ -136,7 +197,7 @@ export const actions = {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Cookie: `jwt=${cookies.get('jwt')}`
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(processedData)
|
body: JSON.stringify({ id: Number(rawUser.id) })
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(apiURL, requestOptions);
|
const res = await fetch(apiURL, requestOptions);
|
||||||
@@ -157,14 +218,15 @@ export const actions = {
|
|||||||
* @param request - The request object
|
* @param request - The request object
|
||||||
* @param fetch - Fetch object from sveltekit
|
* @param fetch - Fetch object from sveltekit
|
||||||
* @param cookies - SvelteKit's cookie object
|
* @param cookies - SvelteKit's cookie object
|
||||||
* @param locals - The local object, housing current subscription
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
subscriptionDelete: async ({ request, fetch, cookies }) => {
|
subscriptionDelete: async ({ request, fetch, cookies }) => {
|
||||||
let formData = await request.formData();
|
let formData = await request.formData();
|
||||||
|
|
||||||
const rawData = formDataToObject(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`;
|
const apiURL = `${BASE_API_URI}/auth/subscriptions`;
|
||||||
|
|
||||||
@@ -176,6 +238,85 @@ export const actions = {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Cookie: `jwt=${cookies.get('jwt')}`
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
},
|
},
|
||||||
|
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);
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
grantBackendAccess: async ({ request, fetch, cookies }) => {
|
||||||
|
let formData = await request.formData();
|
||||||
|
|
||||||
|
const rawFormData = formDataToObject(formData);
|
||||||
|
/** @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`;
|
||||||
|
|
||||||
|
/** @type {RequestInit} */
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'PATCH',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: `jwt=${cookies.get('jwt')}`
|
||||||
|
},
|
||||||
body: JSON.stringify(processedData)
|
body: JSON.stringify(processedData)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
import { applyAction, enhance } from '$app/forms';
|
import { applyAction, enhance } from '$app/forms';
|
||||||
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||||
import { PERMISSIONS } from '$lib/utils/constants';
|
import { PERMISSIONS } from '$lib/utils/constants';
|
||||||
import { defaultSupporter, defaultUser } from '$lib/utils/defaults';
|
import {
|
||||||
|
defaultCar,
|
||||||
|
defaultSubscription,
|
||||||
|
defaultSupporter,
|
||||||
|
defaultUser
|
||||||
|
} from '$lib/utils/defaults';
|
||||||
|
import CarEditForm from '$lib/components/CarEditForm.svelte';
|
||||||
|
|
||||||
/** @type {import('./$types').ActionData} */
|
/** @type {import('./$types').ActionData} */
|
||||||
export let form;
|
export let form;
|
||||||
@@ -16,18 +22,17 @@
|
|||||||
$: ({
|
$: ({
|
||||||
user = [],
|
user = [],
|
||||||
users = [],
|
users = [],
|
||||||
|
cars = [],
|
||||||
licence_categories = [],
|
licence_categories = [],
|
||||||
subscriptions = [],
|
subscriptions = [],
|
||||||
payments = []
|
payments = []
|
||||||
} = $page.data);
|
} = $page.data);
|
||||||
|
|
||||||
let activeSection = 'members';
|
let activeSection = 'members';
|
||||||
/** @type{App.Locals['user'] | null} */
|
|
||||||
let selectedUser = null;
|
/** @type{App.Types['car'] | App.Types['subscription'] | App.Locals['user'] | null} */
|
||||||
/** @type{App.Types['subscription'] | null} */
|
let selected = null;
|
||||||
let selectedSubscription = null;
|
|
||||||
let showSubscriptionModal = false;
|
|
||||||
let showUserModal = false;
|
|
||||||
let searchTerm = '';
|
let searchTerm = '';
|
||||||
|
|
||||||
$: members = users.filter((/** @type{App.Locals['user']} */ user) => {
|
$: members = users.filter((/** @type{App.Locals['user']} */ user) => {
|
||||||
@@ -78,9 +83,7 @@
|
|||||||
user.licence?.number?.toLowerCase()
|
user.licence?.number?.toLowerCase()
|
||||||
].some((field) => field?.includes(term));
|
].some((field) => field?.includes(term));
|
||||||
|
|
||||||
const subscriptionMatch = user.membership?.subscription_model?.name
|
const subscriptionMatch = user.membership?.subscription?.name?.toLowerCase().includes(term);
|
||||||
?.toLowerCase()
|
|
||||||
.includes(term);
|
|
||||||
|
|
||||||
const licenceCategoryMatch = user.licence?.categories?.some((cat) =>
|
const licenceCategoryMatch = user.licence?.categories?.some((cat) =>
|
||||||
cat.category.toLowerCase().includes(term)
|
cat.category.toLowerCase().includes(term)
|
||||||
@@ -95,29 +98,8 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the edit modal for the selected user.
|
|
||||||
* @param {App.Locals['user']} user The user to edit.
|
|
||||||
*/
|
|
||||||
const openEditUserModal = (user) => {
|
|
||||||
selectedUser = user;
|
|
||||||
showUserModal = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the edit modal for the selected subscription.
|
|
||||||
* @param {App.Types['subscription'] | null} subscription The user to edit.
|
|
||||||
*/
|
|
||||||
const openEditSubscriptionModal = (subscription) => {
|
|
||||||
selectedSubscription = subscription;
|
|
||||||
showSubscriptionModal = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
showUserModal = false;
|
selected = null;
|
||||||
showSubscriptionModal = false;
|
|
||||||
selectedUser = null;
|
|
||||||
selectedSubscription = null;
|
|
||||||
if (form) {
|
if (form) {
|
||||||
form.errors = [];
|
form.errors = [];
|
||||||
}
|
}
|
||||||
@@ -163,10 +145,20 @@
|
|||||||
on:click={() => setActiveSection('subscriptions')}
|
on:click={() => setActiveSection('subscriptions')}
|
||||||
>
|
>
|
||||||
<i class="fas fa-clipboard-list"></i>
|
<i class="fas fa-clipboard-list"></i>
|
||||||
{$t('subscription.subscriptions')}
|
{$t('subscriptions.subscriptions')}
|
||||||
<span class="nav-badge">{subscriptions.length}</span>
|
<span class="nav-badge">{subscriptions.length}</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="nav-link {activeSection === 'cars' ? 'active' : ''}"
|
||||||
|
on:click={() => setActiveSection('cars')}
|
||||||
|
>
|
||||||
|
<i class="fas fa-car"></i>
|
||||||
|
{$t('cars')}
|
||||||
|
<span class="nav-badge">{cars.length}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="nav-link {activeSection === 'payments' ? 'active' : ''}"
|
class="nav-link {activeSection === 'payments' ? 'active' : ''}"
|
||||||
@@ -214,7 +206,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn primary" on:click={() => openEditUserModal(defaultUser())}>
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = defaultUser();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
{$t('add_new')}
|
{$t('add_new')}
|
||||||
</button>
|
</button>
|
||||||
@@ -234,6 +231,41 @@
|
|||||||
<th>{$t('user.id')}</th>
|
<th>{$t('user.id')}</th>
|
||||||
<td>{user.id}</td>
|
<td>{user.id}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/grantBackendAccess"
|
||||||
|
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.backend_access', {
|
||||||
|
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} />
|
||||||
|
<button class="button-dark" type="submit">
|
||||||
|
<i class="fas fa-unlock-keyhole"></i>
|
||||||
|
{$t('grant_backend_access')}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('name')}</th>
|
<th>{$t('name')}</th>
|
||||||
<td>{user.first_name} {user.last_name}</td>
|
<td>{user.first_name} {user.last_name}</td>
|
||||||
@@ -243,8 +275,8 @@
|
|||||||
<td>{user.email}</td>
|
<td>{user.email}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('subscription.subscription')}</th>
|
<th>{$t('subscriptions.subscription')}</th>
|
||||||
<td>{user.membership?.subscription_model?.name}</td>
|
<td>{user.membership?.subscription?.name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('status')}</th>
|
<th>{$t('status')}</th>
|
||||||
@@ -253,7 +285,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="btn primary" on:click={() => openEditUserModal(user)}>
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = user;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
{$t('edit')}
|
{$t('edit')}
|
||||||
</button>
|
</button>
|
||||||
@@ -315,7 +352,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn primary" on:click={() => openEditUserModal(defaultSupporter())}>
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = defaultSupporter();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
{$t('add_new')}
|
{$t('add_new')}
|
||||||
</button>
|
</button>
|
||||||
@@ -325,8 +367,7 @@
|
|||||||
{#each filteredSupporters as user}
|
{#each filteredSupporters as user}
|
||||||
<details class="accordion-item">
|
<details class="accordion-item">
|
||||||
<summary class="accordion-header">
|
<summary class="accordion-header">
|
||||||
{user.first_name}
|
{user.company}
|
||||||
{user.last_name}
|
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-content">
|
<div class="accordion-content">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@@ -336,8 +377,8 @@
|
|||||||
<td>{user.first_name} {user.last_name}</td>
|
<td>{user.first_name} {user.last_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('company')}</th>
|
<th>{$t('phone')}</th>
|
||||||
<td>{user.company}</td>
|
<td>{user.phone}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('user.email')}</th>
|
<th>{$t('user.email')}</th>
|
||||||
@@ -350,10 +391,18 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button class="btn primary" on:click={() => openEditUserModal(user)}>
|
{#if hasPrivilige(user, PERMISSIONS.Update)}
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = user;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
{$t('edit')}
|
{$t('edit')}
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Delete)}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/userDelete"
|
action="?/userDelete"
|
||||||
@@ -386,6 +435,7 @@
|
|||||||
{$t('delete')}
|
{$t('delete')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@@ -393,9 +443,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if activeSection === 'subscriptions'}
|
{:else if activeSection === 'subscriptions'}
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>{$t('subscription.subscriptions')}</h2>
|
<h2>{$t('subscriptions.subscriptions')}</h2>
|
||||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||||
<button class="btn primary" on:click={() => openEditSubscriptionModal(null)}>
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = defaultSubscription();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
{$t('add_new')}
|
{$t('add_new')}
|
||||||
</button>
|
</button>
|
||||||
@@ -409,7 +464,7 @@
|
|||||||
<span class="nav-badge"
|
<span class="nav-badge"
|
||||||
>{members.filter(
|
>{members.filter(
|
||||||
(/** @type{App.Locals['user']}*/ user) =>
|
(/** @type{App.Locals['user']}*/ user) =>
|
||||||
user.membership?.subscription_model?.name === subscription.name
|
user.membership?.subscription?.name === subscription.name
|
||||||
).length}</span
|
).length}</span
|
||||||
>
|
>
|
||||||
</summary>
|
</summary>
|
||||||
@@ -417,7 +472,7 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('subscription.monthly_fee')}</th>
|
<th>{$t('subscriptions.monthly_fee')}</th>
|
||||||
<td
|
<td
|
||||||
>{subscription.monthly_fee !== -1
|
>{subscription.monthly_fee !== -1
|
||||||
? subscription.monthly_fee + '€'
|
? subscription.monthly_fee + '€'
|
||||||
@@ -425,7 +480,7 @@
|
|||||||
>
|
>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('subscription.hourly_rate')}</th>
|
<th>{$t('subscriptions.hourly_rate')}</th>
|
||||||
<td
|
<td
|
||||||
>{subscription.hourly_rate !== -1
|
>{subscription.hourly_rate !== -1
|
||||||
? subscription.hourly_rate + '€'
|
? subscription.hourly_rate + '€'
|
||||||
@@ -433,11 +488,11 @@
|
|||||||
>
|
>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
<td>{subscription.included_hours_per_year || 0}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
<td>{subscription.included_hours_per_month || 0}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -445,21 +500,24 @@
|
|||||||
<td>{subscription.details || '-'}</td>
|
<td>{subscription.details || '-'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$t('subscription.conditions')}</th>
|
<th>{$t('subscriptions.conditions')}</th>
|
||||||
<td>{subscription.conditions || '-'}</td>
|
<td>{subscription.conditions || '-'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||||
<button
|
<button
|
||||||
class="btn primary"
|
class="btn primary"
|
||||||
on:click={() => openEditSubscriptionModal(subscription)}
|
on:click={() => {
|
||||||
|
selected = subscription;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
{$t('edit')}
|
{$t('edit')}
|
||||||
</button>
|
</button>
|
||||||
{#if !members.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription_model?.id === subscription.id)}
|
{/if}
|
||||||
|
{#if !members.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription?.id === subscription.id)}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/subscriptionDelete"
|
action="?/subscriptionDelete"
|
||||||
@@ -498,8 +556,115 @@
|
|||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else if activeSection === 'cars'}
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>{$t('cars')}</h2>
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = defaultCar();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
{$t('add_new')}
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="accordion">
|
||||||
|
{#each cars as car}
|
||||||
|
<details class="accordion-item">
|
||||||
|
<summary class="accordion-header">
|
||||||
|
{car.model + ' (' + car.licence_plate + ')'}
|
||||||
|
</summary>
|
||||||
|
<div class="accordion-content">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{$t('car.model')}</th>
|
||||||
|
<td>{car.brand + ' ' + car.model + ' (' + car.color + ')'}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{$t('price')}</th>
|
||||||
|
<td
|
||||||
|
>{car.price + '€'}{car.rate
|
||||||
|
? ' + ' + car.rate + '€/' + $t('month')
|
||||||
|
: ''}</td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{$t('car.damages')}</th>
|
||||||
|
<td>{car.damages?.length || 0}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{$t('insurance')}</th>
|
||||||
|
<td
|
||||||
|
>{car.insurance
|
||||||
|
? car.insurance.company + '(' + car.insurance.reference + ')'
|
||||||
|
: '-'}</td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{$t('car.end_date')}</th>
|
||||||
|
<td>{car.end_date || '-'}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="button-group">
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Update)}
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
on:click={() => {
|
||||||
|
selected = car;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
{$t('edit')}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{#if hasPrivilige(user, PERMISSIONS.Delete)}
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/carDelete"
|
||||||
|
use:enhance={() => {
|
||||||
|
return async ({ result }) => {
|
||||||
|
if (result.type === 'success' || result.type === 'redirect') {
|
||||||
|
await applyAction(result);
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.querySelector('.accordion-content')
|
||||||
|
?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
await applyAction(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
$t('dialog.car_deletion', {
|
||||||
|
values: {
|
||||||
|
name: car.brand + ' ' + car.model + ' (' + car.licence_plate + ')'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
e.preventDefault(); // Cancel form submission if user declines
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -533,28 +698,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showUserModal && selectedUser !== null}
|
{#if selected && 'email' in selected}
|
||||||
|
// user
|
||||||
<Modal on:close={close}>
|
<Modal on:close={close}>
|
||||||
<UserEditForm
|
<UserEditForm
|
||||||
{form}
|
{form}
|
||||||
editor={user}
|
editor={user}
|
||||||
user={selectedUser}
|
user={selected}
|
||||||
{subscriptions}
|
{subscriptions}
|
||||||
{licence_categories}
|
{licence_categories}
|
||||||
on:cancel={close}
|
on:cancel={close}
|
||||||
on:close={close}
|
on:close={close}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
{:else if showSubscriptionModal}
|
{:else if selected && 'monthly_fee' in selected}
|
||||||
|
//subscription
|
||||||
<Modal on:close={close}>
|
<Modal on:close={close}>
|
||||||
<SubscriptionEditForm
|
<SubscriptionEditForm
|
||||||
{form}
|
{form}
|
||||||
{user}
|
{user}
|
||||||
subscription={selectedSubscription}
|
subscription={selected}
|
||||||
on:cancel={close}
|
on:cancel={close}
|
||||||
on:close={close}
|
on:close={close}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
{:else if selected && 'brand' in selected}
|
||||||
|
<Modal on:close={close}>
|
||||||
|
<CarEditForm {form} editor={user} {users} car={selected} on:cancel={close} on:close={close} />
|
||||||
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -568,6 +739,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap; /* Allows wrapping on small screens */
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function load({ locals }) {
|
|||||||
console.log('loading login page');
|
console.log('loading login page');
|
||||||
if (locals.user) {
|
if (locals.user) {
|
||||||
console.log('user is logged in');
|
console.log('user is logged in');
|
||||||
throw redirect(302, '/');
|
throw redirect(302, `${base}/auth/about/${locals.user.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const actions = {
|
|||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
|
|
||||||
// redirect the user
|
// redirect the user
|
||||||
|
console.log('redirecting to: ', `${base}/auth/confirming?message=${response.message}`);
|
||||||
throw redirect(302, `${base}/auth/confirming?message=${response.message}`);
|
throw redirect(302, `${base}/auth/confirming?message=${response.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export const actions = {
|
|||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
|
|
||||||
// redirect the user
|
// redirect the user
|
||||||
|
console.log('redirecting to: ', `${base}/auth/login?message=${response.message}`);
|
||||||
throw redirect(302, `${base}/auth/login?message=${response.message}`);
|
throw redirect(302, `${base}/auth/login?message=${response.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ func main() {
|
|||||||
|
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
err := database.Open(config.DB.Path, config.Recipients.AdminEmail)
|
db, err := database.Open(config.DB.Path, config.Recipients.AdminEmail, config.Env == "development")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Fatalf("Couldn't init database: %v", err)
|
logger.Error.Fatalf("Couldn't init database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := database.Close(); err != nil {
|
if err := database.Close(db); err != nil {
|
||||||
logger.Error.Fatalf("Failed to close database: %v", err)
|
logger.Error.Fatalf("Failed to close database: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go server.Run()
|
go server.Run(db)
|
||||||
|
|
||||||
gracefulShutdown()
|
gracefulShutdown()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -15,7 +17,6 @@ import (
|
|||||||
|
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
|
|
||||||
"GoMembership/internal/utils"
|
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,6 +62,16 @@ type SecurityConfig struct {
|
|||||||
Burst int `json:"Burst" default:"60" envconfig:"BURST_LIMIT"`
|
Burst int `json:"Burst" default:"60" envconfig:"BURST_LIMIT"`
|
||||||
} `json:"RateLimits"`
|
} `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 {
|
type Config struct {
|
||||||
Auth AuthenticationConfig `json:"auth"`
|
Auth AuthenticationConfig `json:"auth"`
|
||||||
Site SiteConfig `json:"site"`
|
Site SiteConfig `json:"site"`
|
||||||
@@ -71,6 +82,7 @@ type Config struct {
|
|||||||
DB DatabaseConfig `json:"db"`
|
DB DatabaseConfig `json:"db"`
|
||||||
SMTP SMTPConfig `json:"smtp"`
|
SMTP SMTPConfig `json:"smtp"`
|
||||||
Security SecurityConfig `json:"security"`
|
Security SecurityConfig `json:"security"`
|
||||||
|
Company CompanyConfig `json:"company"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -84,7 +96,9 @@ var (
|
|||||||
Recipients RecipientsConfig
|
Recipients RecipientsConfig
|
||||||
Env string
|
Env string
|
||||||
Security SecurityConfig
|
Security SecurityConfig
|
||||||
|
Company CompanyConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
var environmentOptions map[string]bool = map[string]bool{
|
var environmentOptions map[string]bool = map[string]bool{
|
||||||
"development": true,
|
"development": true,
|
||||||
"production": true,
|
"production": true,
|
||||||
@@ -99,12 +113,12 @@ func LoadConfig() {
|
|||||||
readFile(&CFG)
|
readFile(&CFG)
|
||||||
readEnv(&CFG)
|
readEnv(&CFG)
|
||||||
logger.Info.Printf("Config file environment: %v", CFGPath)
|
logger.Info.Printf("Config file environment: %v", CFGPath)
|
||||||
csrfSecret, err := utils.GenerateRandomString(32)
|
csrfSecret, err := generateRandomString(32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Fatalf("could not generate CSRF secret: %v", err)
|
logger.Error.Fatalf("could not generate CSRF secret: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtSecret, err := utils.GenerateRandomString(32)
|
jwtSecret, err := generateRandomString(32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Fatalf("could not generate JWT secret: %v", err)
|
logger.Error.Fatalf("could not generate JWT secret: %v", err)
|
||||||
}
|
}
|
||||||
@@ -123,6 +137,7 @@ func LoadConfig() {
|
|||||||
Security = CFG.Security
|
Security = CFG.Security
|
||||||
Env = CFG.Env
|
Env = CFG.Env
|
||||||
Site = CFG.Site
|
Site = CFG.Site
|
||||||
|
Company = CFG.Company
|
||||||
logger.Info.Printf("Config loaded: %#v", CFG)
|
logger.Info.Printf("Config loaded: %#v", CFG)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,3 +175,12 @@ func readEnv(cfg *Config) {
|
|||||||
logger.Error.Fatalf("could not decode env variables: %#v", err)
|
logger.Error.Fatalf("could not decode env variables: %#v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateRandomString(length int) (string, error) {
|
||||||
|
bytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(bytes), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ const (
|
|||||||
AwaitingPaymentStatus
|
AwaitingPaymentStatus
|
||||||
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
MailVerificationSubject = "Nur noch ein kleiner Schritt!"
|
||||||
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||||
|
MailGrantBackendAccessSubject = "Dein Dörpsmobil Hasloh e.V. Zugang"
|
||||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||||
SupporterSubscriptionModelName = "Keins"
|
SupporterSubscriptionName = "Keins"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Licences = struct {
|
var Licences = struct {
|
||||||
@@ -66,20 +67,24 @@ var Priviliges = struct {
|
|||||||
Create int8
|
Create int8
|
||||||
Update int8
|
Update int8
|
||||||
Delete int8
|
Delete int8
|
||||||
|
AccessControl int8
|
||||||
}{
|
}{
|
||||||
View: 2,
|
View: 2,
|
||||||
Update: 4,
|
Update: 4,
|
||||||
Create: 4,
|
Create: 4,
|
||||||
Delete: 4,
|
Delete: 4,
|
||||||
|
AccessControl: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Roles = struct {
|
var Roles = struct {
|
||||||
|
Opponent int8
|
||||||
Supporter int8
|
Supporter int8
|
||||||
Member int8
|
Member int8
|
||||||
Viewer int8
|
Viewer int8
|
||||||
Editor int8
|
Editor int8
|
||||||
Admin int8
|
Admin int8
|
||||||
}{
|
}{
|
||||||
|
Opponent: -5,
|
||||||
Supporter: 0,
|
Supporter: 0,
|
||||||
Member: 1,
|
Member: 1,
|
||||||
Viewer: 2,
|
Viewer: 2,
|
||||||
|
|||||||
116
go-backend/internal/controllers/car_controller.go
Normal file
116
go-backend/internal/controllers/car_controller.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/constants"
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/services"
|
||||||
|
"GoMembership/internal/utils"
|
||||||
|
"GoMembership/pkg/errors"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CarController struct {
|
||||||
|
S services.CarServiceInterface
|
||||||
|
UserService services.UserServiceInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *CarController) Create(c *gin.Context) {
|
||||||
|
requestUser, err := cr.UserService.FromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error extracting user from context in Create car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !requestUser.HasPrivilege(constants.Priviliges.Create) {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to create a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Create), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var newCar models.Car
|
||||||
|
if err := c.ShouldBindJSON(&newCar); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
car, err := cr.S.Create(&newCar)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error creating car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusCreated, car)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *CarController) Update(c *gin.Context) {
|
||||||
|
requestUser, err := cr.UserService.FromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error extracting user from context in Update car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !requestUser.HasPrivilege(constants.Priviliges.Update) {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to update a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Update), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var car models.Car
|
||||||
|
if err := c.ShouldBindJSON(&car); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Error.Printf("updating car: %v", car)
|
||||||
|
updatedCar, err := cr.S.Update(&car)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error updating car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, updatedCar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *CarController) GetAll(c *gin.Context) {
|
||||||
|
requestUser, err := cr.UserService.FromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error extracting user from context in GetAll car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !requestUser.HasPrivilege(constants.Priviliges.View) {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to access car data. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Delete), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cars, err := cr.S.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error getting cars", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"cars": cars,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *CarController) Delete(c *gin.Context) {
|
||||||
|
type input struct {
|
||||||
|
ID uint `json:"id" binding:"required,numeric"`
|
||||||
|
}
|
||||||
|
var deleteData input
|
||||||
|
requestUser, err := cr.UserService.FromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Error extracting user from context in Delete car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !requestUser.HasPrivilege(constants.Priviliges.Delete) {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to delete a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Delete), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&deleteData); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, "Car deleted")
|
||||||
|
}
|
||||||
@@ -48,9 +48,11 @@ var (
|
|||||||
Uc *UserController
|
Uc *UserController
|
||||||
Mc *MembershipController
|
Mc *MembershipController
|
||||||
Cc *ContactController
|
Cc *ContactController
|
||||||
|
AdminCookie *http.Cookie
|
||||||
|
MemberCookie *http.Cookie
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSuite(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
_ = deleteTestDB("test.db")
|
_ = deleteTestDB("test.db")
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
@@ -85,7 +87,8 @@ func TestSuite(t *testing.T) {
|
|||||||
log.Fatalf("Error setting environment variable: %v", err)
|
log.Fatalf("Error setting environment variable: %v", err)
|
||||||
}
|
}
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
if err := database.Open("test.db", config.Recipients.AdminEmail); err != nil {
|
db, err := database.Open("test.db", config.Recipients.AdminEmail, true)
|
||||||
|
if err != nil {
|
||||||
log.Fatalf("Failed to create DB: %#v", err)
|
log.Fatalf("Failed to create DB: %#v", err)
|
||||||
}
|
}
|
||||||
utils.SMTPStart(Host, Port)
|
utils.SMTPStart(Host, Port)
|
||||||
@@ -97,17 +100,16 @@ func TestSuite(t *testing.T) {
|
|||||||
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
||||||
|
|
||||||
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
|
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}
|
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
|
||||||
|
|
||||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||||
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
userService := &services.UserService{DB: db, Licences: licenceRepo}
|
||||||
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
|
||||||
|
|
||||||
licenceService := &services.LicenceService{Repo: licenceRepo}
|
licenceService := &services.LicenceService{Repo: licenceRepo}
|
||||||
|
|
||||||
Uc = &UserController{Service: userService, LicenceService: licenceService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
Uc = &UserController{Service: userService, LicenceService: licenceService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
||||||
Mc = &MembershipController{UserController: &MockUserController{}, Service: *membershipService}
|
Mc = &MembershipController{UserService: userService, Service: membershipService}
|
||||||
Cc = &ContactController{EmailService: emailService}
|
Cc = &ContactController{EmailService: emailService}
|
||||||
|
|
||||||
if err := initSubscriptionPlans(); err != nil {
|
if err := initSubscriptionPlans(); err != nil {
|
||||||
@@ -117,6 +119,7 @@ func TestSuite(t *testing.T) {
|
|||||||
if err := initLicenceCategories(); err != nil {
|
if err := initLicenceCategories(); err != nil {
|
||||||
log.Fatalf("Failed to init Categories: %v", err)
|
log.Fatalf("Failed to init Categories: %v", err)
|
||||||
}
|
}
|
||||||
|
password := "securepassword"
|
||||||
admin := models.User{
|
admin := models.User{
|
||||||
FirstName: "Ad",
|
FirstName: "Ad",
|
||||||
LastName: "min",
|
LastName: "min",
|
||||||
@@ -128,15 +131,24 @@ func TestSuite(t *testing.T) {
|
|||||||
ZipCode: "12345",
|
ZipCode: "12345",
|
||||||
City: "SampleCity",
|
City: "SampleCity",
|
||||||
Status: constants.ActiveStatus,
|
Status: constants.ActiveStatus,
|
||||||
RoleID: 8,
|
Password: password,
|
||||||
}
|
Notes: "",
|
||||||
admin.SetPassword("securepassword")
|
RoleID: constants.Roles.Admin,
|
||||||
database.DB.Create(&admin)
|
Consents: nil,
|
||||||
validation.SetupValidators()
|
Verifications: nil,
|
||||||
|
Membership: nil,
|
||||||
|
BankAccount: nil,
|
||||||
|
Licence: &models.Licence{
|
||||||
|
Status: constants.UnverifiedStatus,
|
||||||
|
}}
|
||||||
|
admin.Create(db)
|
||||||
|
validation.SetupValidators(db)
|
||||||
t.Run("userController", func(t *testing.T) {
|
t.Run("userController", func(t *testing.T) {
|
||||||
testUserController(t)
|
testUserController(t)
|
||||||
})
|
})
|
||||||
|
t.Run("Password_Controller", func(t *testing.T) {
|
||||||
|
|
||||||
|
})
|
||||||
t.Run("SQL_Injection", func(t *testing.T) {
|
t.Run("SQL_Injection", func(t *testing.T) {
|
||||||
testSQLInjectionAttempt(t)
|
testSQLInjectionAttempt(t)
|
||||||
})
|
})
|
||||||
@@ -152,7 +164,6 @@ func TestSuite(t *testing.T) {
|
|||||||
t.Run("XSSAttempt", func(t *testing.T) {
|
t.Run("XSSAttempt", func(t *testing.T) {
|
||||||
testXSSAttempt(t)
|
testXSSAttempt(t)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := utils.SMTPStop(); err != nil {
|
if err := utils.SMTPStop(); err != nil {
|
||||||
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
|
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
|
||||||
}
|
}
|
||||||
@@ -192,7 +203,7 @@ func initLicenceCategories() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initSubscriptionPlans() error {
|
func initSubscriptionPlans() error {
|
||||||
subscriptions := []models.SubscriptionModel{
|
subscriptions := []models.Subscription{
|
||||||
{
|
{
|
||||||
Name: "Basic",
|
Name: "Basic",
|
||||||
Details: "Test Plan",
|
Details: "Test Plan",
|
||||||
@@ -272,16 +283,33 @@ func getBaseUser() models.User {
|
|||||||
ZipCode: "25474",
|
ZipCode: "25474",
|
||||||
City: "Hasloh",
|
City: "Hasloh",
|
||||||
Phone: "01738484993",
|
Phone: "01738484993",
|
||||||
BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"},
|
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||||
Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
|
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
|
||||||
Licence: nil,
|
Licence: nil,
|
||||||
ProfilePicture: "",
|
|
||||||
Password: "passw@#$#%$!-ord123",
|
Password: "passw@#$#%$!-ord123",
|
||||||
Company: "",
|
Company: "",
|
||||||
RoleID: 8,
|
RoleID: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseSupporter() models.User {
|
||||||
|
return models.User{
|
||||||
|
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
FirstName: "John",
|
||||||
|
LastName: "Rich",
|
||||||
|
Email: "john.supporter@example.com",
|
||||||
|
Address: "Pablo Escobar Str. 4",
|
||||||
|
ZipCode: "25474",
|
||||||
|
City: "Hasloh",
|
||||||
|
Phone: "01738484993",
|
||||||
|
BankAccount: &models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||||
|
Membership: &models.Membership{Subscription: models.Subscription{Name: "Basic"}},
|
||||||
|
Licence: nil,
|
||||||
|
Password: "passw@#$#%$!-ord123",
|
||||||
|
Company: "",
|
||||||
|
RoleID: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
func deleteTestDB(dbPath string) error {
|
func deleteTestDB(dbPath string) error {
|
||||||
err := os.Remove(dbPath)
|
err := os.Remove(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LicenceController struct {
|
type LicenceController struct {
|
||||||
Service services.LicenceService
|
Service services.LicenceServiceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LicenceController) GetAllCategories(c *gin.Context) {
|
func (lc *LicenceController) GetAllCategories(c *gin.Context) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestGetAllCategories_Success(t *testing.T) {
|
|||||||
service := &services.LicenceService{Repo: mockRepo}
|
service := &services.LicenceService{Repo: mockRepo}
|
||||||
|
|
||||||
// Create controller with service
|
// Create controller with service
|
||||||
lc := &LicenceController{Service: *service}
|
lc := &LicenceController{Service: service}
|
||||||
|
|
||||||
// Setup router and request
|
// Setup router and request
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
@@ -76,7 +76,7 @@ func TestGetAllCategories_Error(t *testing.T) {
|
|||||||
service := &services.LicenceService{Repo: mockRepo}
|
service := &services.LicenceService{Repo: mockRepo}
|
||||||
|
|
||||||
// Create controller with service
|
// Create controller with service
|
||||||
lc := &LicenceController{Service: *service}
|
lc := &LicenceController{Service: service}
|
||||||
|
|
||||||
// Setup router and request
|
// Setup router and request
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|||||||
@@ -16,47 +16,40 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MembershipController struct {
|
type MembershipController struct {
|
||||||
Service services.MembershipService
|
Service services.MembershipServiceInterface
|
||||||
UserController interface {
|
UserService services.UserServiceInterface
|
||||||
ExtractUserFromContext(*gin.Context) (*models.User, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MembershipData struct {
|
|
||||||
// APIKey string `json:"api_key"`
|
|
||||||
Subscription models.SubscriptionModel `json:"subscription"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
||||||
var regData MembershipData
|
|
||||||
|
|
||||||
requestUser, err := mc.UserController.ExtractUserFromContext(c)
|
requestUser, err := mc.UserService.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in subscription registrationHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in subscription registrationHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Create) {
|
if !requestUser.HasPrivilege(constants.Priviliges.Create) {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to register subscription", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to register subscription", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
var subscription models.Subscription
|
||||||
|
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Subscription
|
// Register Subscription
|
||||||
id, err := mc.Service.RegisterSubscription(®Data.Subscription)
|
id, err := mc.Service.RegisterSubscription(&subscription)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
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 {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
logger.Info.Printf("registering subscription: %+v", regData)
|
logger.Info.Printf("registering subscription: %+v", subscription)
|
||||||
c.JSON(http.StatusCreated, gin.H{
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"id": id,
|
"id": id,
|
||||||
@@ -64,32 +57,31 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MembershipController) UpdateHandler(c *gin.Context) {
|
func (mc *MembershipController) UpdateHandler(c *gin.Context) {
|
||||||
var regData MembershipData
|
|
||||||
|
|
||||||
requestUser, err := mc.UserController.ExtractUserFromContext(c)
|
requestUser, err := mc.UserService.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in subscription Updatehandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in subscription Updatehandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) {
|
if !requestUser.HasPrivilege(constants.Priviliges.Update) {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
var subscription models.Subscription
|
||||||
|
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// update Subscription
|
// update Subscription
|
||||||
logger.Info.Printf("Updating subscription %v", regData.Subscription.Name)
|
logger.Info.Printf("Updating subscription %v", subscription.Name)
|
||||||
id, err := mc.Service.UpdateSubscription(®Data.Subscription)
|
id, err := mc.Service.UpdateSubscription(&subscription)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HandleSubscriptionUpdateError(c, err)
|
utils.HandleSubscriptionUpdateError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info.Printf("updating subscription: %+v", regData)
|
|
||||||
c.JSON(http.StatusAccepted, gin.H{
|
c.JSON(http.StatusAccepted, gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"id": id,
|
"id": id,
|
||||||
@@ -98,30 +90,28 @@ func (mc *MembershipController) UpdateHandler(c *gin.Context) {
|
|||||||
|
|
||||||
func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
|
func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
|
||||||
type deleteData struct {
|
type deleteData struct {
|
||||||
Subscription struct {
|
ID uint `json:"id" binding:"required,numeric,safe_content"`
|
||||||
ID uint `json:"id"`
|
Name string `json:"name" binding:"required,safe_content"`
|
||||||
Name string `json:"name"`
|
|
||||||
} `json:"subscription"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data deleteData
|
var subscription deleteData
|
||||||
requestUser, err := mc.UserController.ExtractUserFromContext(c)
|
requestUser, err := mc.UserService.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in subscription deleteSubscription", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in subscription deleteSubscription", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Delete) {
|
if !requestUser.HasPrivilege(constants.Priviliges.Delete) {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mc.Service.DeleteSubscription(&data.Subscription.ID, &data.Subscription.Name); err != nil {
|
if err := mc.Service.DeleteSubscription(&subscription.ID, &subscription.Name); err != nil {
|
||||||
utils.HandleSubscriptionDeleteError(c, err)
|
utils.HandleSubscriptionDeleteError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -132,7 +122,7 @@ func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
|
|||||||
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
|
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
|
||||||
subscriptions, err := mc.Service.GetSubscriptions(nil)
|
subscriptions, err := mc.Service.GetSubscriptions(nil)
|
||||||
if err != 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"GoMembership/internal/constants"
|
|
||||||
"GoMembership/internal/database"
|
"GoMembership/internal/database"
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RegisterSubscriptionTest struct {
|
type RegisterSubscriptionTest struct {
|
||||||
|
SetupCookie func(r *http.Request)
|
||||||
WantDBData map[string]interface{}
|
WantDBData map[string]interface{}
|
||||||
Input string
|
Input string
|
||||||
Name string
|
Name string
|
||||||
@@ -23,6 +23,7 @@ type RegisterSubscriptionTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateSubscriptionTest struct {
|
type UpdateSubscriptionTest struct {
|
||||||
|
SetupCookie func(r *http.Request)
|
||||||
WantDBData map[string]interface{}
|
WantDBData map[string]interface{}
|
||||||
Input string
|
Input string
|
||||||
Name string
|
Name string
|
||||||
@@ -31,6 +32,7 @@ type UpdateSubscriptionTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeleteSubscriptionTest struct {
|
type DeleteSubscriptionTest struct {
|
||||||
|
SetupCookie func(r *http.Request)
|
||||||
WantDBData map[string]interface{}
|
WantDBData map[string]interface{}
|
||||||
Input string
|
Input string
|
||||||
Name string
|
Name string
|
||||||
@@ -38,29 +40,8 @@ type DeleteSubscriptionTest struct {
|
|||||||
Assert bool
|
Assert bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockUserController struct {
|
|
||||||
UserController // Embed the UserController
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUserController) ExtractUserFromContext(c *gin.Context) (*models.User, error) {
|
|
||||||
return &models.User{
|
|
||||||
ID: 1,
|
|
||||||
FirstName: "Admin",
|
|
||||||
LastName: "User",
|
|
||||||
Email: "admin@test.com",
|
|
||||||
RoleID: constants.Roles.Admin,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupMockAuth() {
|
|
||||||
// Create and assign the mock controller
|
|
||||||
mockController := &MockUserController{}
|
|
||||||
Mc.UserController = mockController
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMembershipController(t *testing.T) {
|
func testMembershipController(t *testing.T) {
|
||||||
|
|
||||||
setupMockAuth()
|
|
||||||
tests := getSubscriptionRegistrationData()
|
tests := getSubscriptionRegistrationData()
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
logger.Error.Print("==============================================================")
|
logger.Error.Print("==============================================================")
|
||||||
@@ -101,6 +82,7 @@ func (rt *RegisterSubscriptionTest) SetupContext() (*gin.Context, *httptest.Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rt *RegisterSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
func (rt *RegisterSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
||||||
|
rt.SetupCookie(c.Request)
|
||||||
Mc.RegisterSubscription(c)
|
Mc.RegisterSubscription(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +113,7 @@ func (ut *UpdateSubscriptionTest) SetupContext() (*gin.Context, *httptest.Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ut *UpdateSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
func (ut *UpdateSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
||||||
|
ut.SetupCookie(c.Request)
|
||||||
Mc.UpdateHandler(c)
|
Mc.UpdateHandler(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +133,7 @@ func (dt *DeleteSubscriptionTest) SetupContext() (*gin.Context, *httptest.Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dt *DeleteSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
func (dt *DeleteSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
|
||||||
|
dt.SetupCookie(c.Request)
|
||||||
Mc.DeleteSubscription(c)
|
Mc.DeleteSubscription(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,18 +148,16 @@ func (dt *DeleteSubscriptionTest) ValidateResult() error {
|
|||||||
return validateSubscription(dt.Assert, dt.WantDBData)
|
return validateSubscription(dt.Assert, dt.WantDBData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBaseSubscription() MembershipData {
|
func getBaseSubscription() models.Subscription {
|
||||||
return MembershipData{
|
return models.Subscription{
|
||||||
// APIKey: config.Auth.APIKEY,
|
|
||||||
Subscription: models.SubscriptionModel{
|
|
||||||
Name: "Premium",
|
Name: "Premium",
|
||||||
Details: "A subscription detail",
|
Details: "A subscription detail",
|
||||||
MonthlyFee: 12.0,
|
MonthlyFee: 12.0,
|
||||||
HourlyRate: 14.0,
|
HourlyRate: 14.0,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func customizeSubscription(customize func(MembershipData) MembershipData) MembershipData {
|
|
||||||
|
func customizeSubscription(customize func(models.Subscription) models.Subscription) models.Subscription {
|
||||||
subscription := getBaseSubscription()
|
subscription := getBaseSubscription()
|
||||||
return customize(subscription)
|
return customize(subscription)
|
||||||
}
|
}
|
||||||
@@ -183,61 +165,95 @@ func customizeSubscription(customize func(MembershipData) MembershipData) Member
|
|||||||
func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
||||||
return []RegisterSubscriptionTest{
|
return []RegisterSubscriptionTest{
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Missing details should fail",
|
Name: "Missing details should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
|
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Details = ""
|
subscription.Details = ""
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Missing model name should fail",
|
Name: "Missing model name should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": ""},
|
WantDBData: map[string]interface{}{"name": ""},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = ""
|
subscription.Name = ""
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Negative monthly fee should fail",
|
Name: "Negative monthly fee should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
|
||||||
sub.Subscription.MonthlyFee = -10.0
|
sub.MonthlyFee = -10.0
|
||||||
return sub
|
return sub
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Negative hourly rate should fail",
|
Name: "Negative hourly rate should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
Input: GenerateInputJSON(customizeSubscription(func(sub models.Subscription) models.Subscription {
|
||||||
sub.Subscription.HourlyRate = -1.0
|
sub.HourlyRate = -1.0
|
||||||
return sub
|
return sub
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(MemberCookie)
|
||||||
|
},
|
||||||
|
Name: "correct entry but not authorized",
|
||||||
|
WantResponse: http.StatusUnauthorized,
|
||||||
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
|
Assert: false,
|
||||||
|
Input: GenerateInputJSON(
|
||||||
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
|
subscription.Conditions = "Some Condition"
|
||||||
|
subscription.IncludedPerYear = 0
|
||||||
|
subscription.IncludedPerMonth = 1
|
||||||
|
return subscription
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "correct entry should pass",
|
Name: "correct entry should pass",
|
||||||
WantResponse: http.StatusCreated,
|
WantResponse: http.StatusCreated,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Conditions = "Some Condition"
|
subscription.Conditions = "Some Condition"
|
||||||
subscription.Subscription.IncludedPerYear = 0
|
subscription.IncludedPerYear = 0
|
||||||
subscription.Subscription.IncludedPerMonth = 1
|
subscription.IncludedPerMonth = 1
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Duplicate subscription name should fail",
|
Name: "Duplicate subscription name should fail",
|
||||||
WantResponse: http.StatusConflict,
|
WantResponse: http.StatusConflict,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
@@ -250,82 +266,120 @@ func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
|
|||||||
func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
||||||
return []UpdateSubscriptionTest{
|
return []UpdateSubscriptionTest{
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Modified Monthly Fee, should fail",
|
Name: "Modified Monthly Fee, should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
|
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.MonthlyFee = 123.0
|
subscription.MonthlyFee = 123.0
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Missing ID, should fail",
|
Name: "Missing ID, should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.ID = 0
|
subscription.ID = 0
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Modified Hourly Rate, should fail",
|
Name: "Modified Hourly Rate, should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
|
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.HourlyRate = 3254.0
|
subscription.HourlyRate = 3254.0
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "IncludedPerYear changed, should fail",
|
Name: "IncludedPerYear changed, should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
|
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.IncludedPerYear = 9873.0
|
subscription.IncludedPerYear = 9873.0
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "IncludedPerMonth changed, should fail",
|
Name: "IncludedPerMonth changed, should fail",
|
||||||
WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
|
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.IncludedPerMonth = 23415.0
|
subscription.IncludedPerMonth = 23415.0
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Update non-existent subscription should fail",
|
Name: "Update non-existent subscription should fail",
|
||||||
WantResponse: http.StatusNotFound,
|
WantResponse: http.StatusNotFound,
|
||||||
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = "NonExistentSubscription"
|
subscription.Name = "NonExistentSubscription"
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(MemberCookie)
|
||||||
|
},
|
||||||
|
Name: "Correct Update but unauthorized",
|
||||||
|
WantResponse: http.StatusUnauthorized,
|
||||||
|
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
|
||||||
|
Assert: false,
|
||||||
|
Input: GenerateInputJSON(
|
||||||
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
|
subscription.Details = "Altered Details"
|
||||||
|
subscription.Conditions = "Some Condition"
|
||||||
|
subscription.IncludedPerYear = 0
|
||||||
|
subscription.IncludedPerMonth = 1
|
||||||
|
return subscription
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Correct Update should pass",
|
Name: "Correct Update should pass",
|
||||||
WantResponse: http.StatusAccepted,
|
WantResponse: http.StatusAccepted,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
|
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Details = "Altered Details"
|
subscription.Details = "Altered Details"
|
||||||
subscription.Subscription.Conditions = "Some Condition"
|
subscription.Conditions = "Some Condition"
|
||||||
subscription.Subscription.IncludedPerYear = 0
|
subscription.IncludedPerYear = 0
|
||||||
subscription.Subscription.IncludedPerMonth = 1
|
subscription.IncludedPerMonth = 1
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -334,58 +388,84 @@ func getSubscriptionUpdateData() []UpdateSubscriptionTest {
|
|||||||
|
|
||||||
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
|
||||||
|
|
||||||
var premiumSub, basicSub models.SubscriptionModel
|
var premiumSub, basicSub models.Subscription
|
||||||
database.DB.Where("name = ?", "Premium").First(&premiumSub)
|
database.DB.Where("name = ?", "Premium").First(&premiumSub)
|
||||||
database.DB.Where("name = ?", "Basic").First(&basicSub)
|
database.DB.Where("name = ?", "Basic").First(&basicSub)
|
||||||
|
|
||||||
logger.Error.Printf("premiumSub.ID: %v", premiumSub.ID)
|
|
||||||
logger.Error.Printf("basicSub.ID: %v", basicSub.ID)
|
|
||||||
return []DeleteSubscriptionTest{
|
return []DeleteSubscriptionTest{
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Delete non-existent subscription should fail",
|
Name: "Delete non-existent subscription should fail",
|
||||||
WantResponse: http.StatusNotFound,
|
WantResponse: http.StatusNotFound,
|
||||||
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = "NonExistentSubscription"
|
subscription.Name = "NonExistentSubscription"
|
||||||
subscription.Subscription.ID = basicSub.ID
|
subscription.ID = basicSub.ID
|
||||||
|
logger.Error.Printf("subscription to delete: %#v", subscription)
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Delete subscription without name should fail",
|
Name: "Delete subscription without name should fail",
|
||||||
WantResponse: http.StatusExpectationFailed,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": ""},
|
WantDBData: map[string]interface{}{"name": ""},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = ""
|
subscription.Name = ""
|
||||||
subscription.Subscription.ID = basicSub.ID
|
subscription.ID = basicSub.ID
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Delete subscription with users should fail",
|
Name: "Delete subscription with users should fail",
|
||||||
WantResponse: http.StatusExpectationFailed,
|
WantResponse: http.StatusExpectationFailed,
|
||||||
WantDBData: map[string]interface{}{"name": "Basic"},
|
WantDBData: map[string]interface{}{"name": "Basic"},
|
||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = "Basic"
|
subscription.Name = "Basic"
|
||||||
subscription.Subscription.ID = basicSub.ID
|
subscription.ID = basicSub.ID
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(MemberCookie)
|
||||||
|
},
|
||||||
|
Name: "Delete valid subscription should succeed",
|
||||||
|
WantResponse: http.StatusUnauthorized,
|
||||||
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
|
Assert: true,
|
||||||
|
Input: GenerateInputJSON(
|
||||||
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
|
subscription.Name = "Premium"
|
||||||
|
subscription.ID = premiumSub.ID
|
||||||
|
return subscription
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SetupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
},
|
||||||
Name: "Delete valid subscription should succeed",
|
Name: "Delete valid subscription should succeed",
|
||||||
WantResponse: http.StatusOK,
|
WantResponse: http.StatusOK,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
customizeSubscription(func(subscription MembershipData) MembershipData {
|
customizeSubscription(func(subscription models.Subscription) models.Subscription {
|
||||||
subscription.Subscription.Name = "Premium"
|
subscription.Name = "Premium"
|
||||||
subscription.Subscription.ID = premiumSub.ID
|
subscription.ID = premiumSub.ID
|
||||||
return subscription
|
return subscription
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,59 @@ import (
|
|||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (uc *UserController) CreatePasswordHandler(c *gin.Context) {
|
||||||
|
|
||||||
|
requestUser, err := uc.Service.FromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't get User from Request Context", http.StatusBadRequest, errors.Responses.Fields.General, errors.Responses.Keys.NoAuthToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !requestUser.IsAdmin() {
|
||||||
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Requesting user not authorized to grant user access", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Expected data from the user
|
||||||
|
var input struct {
|
||||||
|
User struct {
|
||||||
|
ID uint `json:"id" binding:"required,numeric"`
|
||||||
|
} `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// find user
|
||||||
|
user, err := uc.Service.FromID(&input.User.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "couldn't get user by id", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate user and reset Verification
|
||||||
|
user.Status = constants.DisabledStatus
|
||||||
|
v, err := user.SetVerification(constants.VerificationTypes.Password)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(c, err, "couldn't set verification", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := uc.Service.Update(user); err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't update user in createPasswordHandler", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// send email
|
||||||
|
if err := uc.EmailService.SendGrantBackendAccessEmail(user, &v.VerificationToken); err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't send grant backend access email", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusAccepted, gin.H{
|
||||||
|
"message": "password_change_requested",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
|
func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
|
||||||
|
|
||||||
// Expected data from the user
|
// Expected data from the user
|
||||||
@@ -24,27 +77,30 @@ func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// find user
|
// find user
|
||||||
db_user, err := uc.Service.GetUserByEmail(input.Email)
|
user, err := uc.Service.FromEmail(&input.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "couldn't get user by email", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
utils.RespondWithError(c, err, "couldn't get user by email", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user may change the password
|
// check if user may change the password
|
||||||
if db_user.Status <= constants.DisabledStatus {
|
if !user.IsVerified() {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "User password change request denied, user is disabled", http.StatusForbidden, errors.Responses.Fields.Login, errors.Responses.Keys.UserDisabled)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "User password change request denied, user is not verified or disabled", http.StatusForbidden, errors.Responses.Fields.Login, errors.Responses.Keys.UserDisabled)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create token
|
user.Status = constants.DisabledStatus
|
||||||
token, err := uc.Service.HandlePasswordChangeRequest(db_user)
|
v, err := user.SetVerification(constants.VerificationTypes.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "couldn't handle password change request", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
utils.RespondWithError(c, err, "couldn't set verification", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := uc.Service.Update(user); err != nil {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't update user in createPasswordHandler", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
// send email
|
// send email
|
||||||
if err := uc.EmailService.SendChangePasswordEmail(db_user, &token); err != nil {
|
if err := uc.EmailService.SendChangePasswordEmail(user, &v.VerificationToken); err != nil {
|
||||||
utils.RespondWithError(c, err, "Couldn't send change password email", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
utils.RespondWithError(c, err, "Couldn't send change password email", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,27 +121,24 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
|
|||||||
utils.RespondWithError(c, err, "Invalid user ID", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.InvalidUserID)
|
utils.RespondWithError(c, err, "Invalid user ID", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.InvalidUserID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userID := uint(userIDint)
|
||||||
if err := c.ShouldBindJSON(&input); err != nil {
|
user, err := uc.Service.FromID(&userID)
|
||||||
utils.HandleValidationError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
verification, err := uc.Service.VerifyUser(&input.Token, &constants.VerificationTypes.Password)
|
|
||||||
if err != nil || uint(userIDint) != verification.UserID {
|
|
||||||
utils.HandleVerifyUserError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := uc.Service.GetUserByID(verification.UserID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.UserNotFoundWrongPassword)
|
utils.RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.UserNotFoundWrongPassword)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
user.Status = constants.ActiveStatus
|
user.Status = constants.ActiveStatus
|
||||||
user.Verification = *verification
|
|
||||||
user.ID = verification.UserID
|
|
||||||
user.Password = input.Password
|
user.Password = input.Password
|
||||||
|
|
||||||
// Get Gin's binding validator engine with all registered validators
|
// Get Gin's binding validator engine with all registered validators
|
||||||
@@ -96,7 +149,7 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
|
|||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = uc.Service.UpdateUser(user)
|
_, err = uc.Service.Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HandleUserUpdateError(c, err)
|
utils.HandleUserUpdateError(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
211
go-backend/internal/controllers/user_Password_test.go
Normal file
211
go-backend/internal/controllers/user_Password_test.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/config"
|
||||||
|
"GoMembership/internal/constants"
|
||||||
|
"GoMembership/internal/database"
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/utils"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestContext struct {
|
||||||
|
router *gin.Engine
|
||||||
|
response *httptest.ResponseRecorder
|
||||||
|
user *models.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestContext() (*TestContext, error) {
|
||||||
|
testEmail := "john.doe@example.com"
|
||||||
|
user, err := Uc.Service.FromEmail(&testEmail)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("error fetching user: %#v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &TestContext{
|
||||||
|
router: gin.Default(),
|
||||||
|
response: httptest.NewRecorder(),
|
||||||
|
user: user,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func testCreatePasswordHandler(t *testing.T) {
|
||||||
|
invalidCookie := http.Cookie{
|
||||||
|
Name: "jwt",
|
||||||
|
Value: "invalid.token.here",
|
||||||
|
}
|
||||||
|
tc, err := setupTestContext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tc.router.POST("/password", Uc.CreatePasswordHandler)
|
||||||
|
requestBody := map[string]interface{}{
|
||||||
|
"user": map[string]interface{}{
|
||||||
|
"id": tc.user.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(requestBody)
|
||||||
|
t.Run("successful password creation request from admin", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(AdminCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
assert.Equal(t, http.StatusAccepted, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"message":"password_change_requested"}`, tc.response.Body.String())
|
||||||
|
err = checkEmailDelivery(tc.user, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// test token and password change
|
||||||
|
testChangePassword(t, tc)
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("failed password creation request from member", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(MemberCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"errors":[{"field":"user.user","key":"server.error.unauthorized"}]}`, tc.response.Body.String())
|
||||||
|
err = checkEmailDelivery(tc.user, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("failed password creation request for invalid cookie", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
req.AddCookie(&invalidCookie)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)
|
||||||
|
err = checkEmailDelivery(tc.user, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
requestBody := map[string]interface{}{
|
||||||
|
"password": "new-pas9247A@!sword",
|
||||||
|
"token": verification.VerificationToken,
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(requestBody)
|
||||||
|
tc.router.PUT("/users/:id/password", Uc.ChangePassword)
|
||||||
|
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
t.Run("valid password change", func(t *testing.T) {
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("PUT", fmt.Sprintf("/users/%v/password", tc.user.ID), bytes.NewBuffer(body))
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, tc.response.Code)
|
||||||
|
assert.JSONEq(t, `{"message":"password_changed"}`, tc.response.Body.String())
|
||||||
|
})
|
||||||
|
tc.response = httptest.NewRecorder()
|
||||||
|
//User should now be deactivated. Should lack privileges.
|
||||||
|
t.Run("user lacks privileges", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
logger.Error.Printf("Test results for %#v", t.Name())
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
assert.Contains(t, tc.response.Body.String(), `server.error.no_auth_token`)
|
||||||
|
|
||||||
|
})
|
||||||
|
logger.Error.Printf("__________END RESULTS---------")
|
||||||
|
t.Run("invalid user ID", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("PUT", "/users/invalid/password", nil)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
})
|
||||||
|
t.Run("non existant user ID", func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("PUT", "/users/999/password", nil)
|
||||||
|
tc.router.ServeHTTP(tc.response, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, tc.response.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkEmailDelivery(user *models.User, wantsSuccess bool) error {
|
||||||
|
|
||||||
|
//check for email delivery
|
||||||
|
messages := utils.SMTPGetMessages()
|
||||||
|
for _, message := range messages {
|
||||||
|
mail, err := utils.DecodeMail(message.MsgRequest())
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("Error in validateUser: %#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.Contains(mail.Subject, constants.MailChangePasswordSubject) || strings.Contains(mail.Subject, constants.MailGrantBackendAccessSubject) {
|
||||||
|
|
||||||
|
if err := checkPasswordMail(mail, user); err != nil && wantsSuccess {
|
||||||
|
logger.Error.Printf("Error in checkEmailDelivery mail: %#v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Subject not expected: %v", mail.Subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPasswordMail(message *utils.Email, user *models.User) error {
|
||||||
|
var verification models.Verification
|
||||||
|
result := database.DB.Where("user_id = ? AND type = ?", user.ID, constants.VerificationTypes.Password).First(&verification)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
logger.Error.Printf("user id: %v token: %#v", user.ID, verification.VerificationToken)
|
||||||
|
re := regexp.MustCompile(`"([^"]*token[^"]*)"`)
|
||||||
|
|
||||||
|
// Find the matching URL in the email content
|
||||||
|
match := re.FindStringSubmatch(message.Body)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return fmt.Errorf("No change Password link found in email body: %#v", message.Body)
|
||||||
|
}
|
||||||
|
tokenURL, err := url.QueryUnescape(match[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error decoding URL: %v", err)
|
||||||
|
}
|
||||||
|
logger.Info.Printf("TokenURL: %#v", tokenURL)
|
||||||
|
|
||||||
|
if !strings.Contains(message.To, user.Email) {
|
||||||
|
return fmt.Errorf("Password Information didn't reach user! Recipient was: %v instead of %v", message.To, user.Email)
|
||||||
|
}
|
||||||
|
if !strings.Contains(message.From, config.SMTP.User) {
|
||||||
|
return fmt.Errorf("Password Information was sent from unexpected address! Sender was: %v instead of %v", message.From, config.SMTP.User)
|
||||||
|
}
|
||||||
|
//Check if all the relevant data has been passed to the mail.
|
||||||
|
if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
|
||||||
|
return fmt.Errorf("User first and last name(%v) has not been rendered in password mail.", user.FirstName+" "+user.LastName)
|
||||||
|
}
|
||||||
|
if !strings.Contains(message.Body, verification.VerificationToken) {
|
||||||
|
return fmt.Errorf("Token(%v) has not been rendered in password mail.", verification.VerificationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Trim(tokenURL, " ") != fmt.Sprintf("%v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken) {
|
||||||
|
return fmt.Errorf("Token has not been rendered correctly in password mail: %v%v/auth/password/change/%v?token=%v", config.Site.BaseURL, config.Site.FrontendPath, user.ID, verification.VerificationToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -9,11 +9,14 @@ import (
|
|||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
"GoMembership/internal/validation"
|
"GoMembership/internal/validation"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
@@ -25,7 +28,7 @@ type UserController struct {
|
|||||||
ConsentService services.ConsentServiceInterface
|
ConsentService services.ConsentServiceInterface
|
||||||
BankAccountService services.BankAccountServiceInterface
|
BankAccountService services.BankAccountServiceInterface
|
||||||
MembershipService services.MembershipServiceInterface
|
MembershipService services.MembershipServiceInterface
|
||||||
LicenceService services.LicenceInterface
|
LicenceService services.LicenceServiceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationData struct {
|
type RegistrationData struct {
|
||||||
@@ -33,7 +36,7 @@ type RegistrationData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
||||||
requestUser, err := uc.ExtractUserFromContext(c)
|
requestUser, err := uc.Service.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
@@ -46,19 +49,20 @@ func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
|||||||
|
|
||||||
func (uc *UserController) GetAllUsers(c *gin.Context) {
|
func (uc *UserController) GetAllUsers(c *gin.Context) {
|
||||||
|
|
||||||
requestUser, err := uc.ExtractUserFromContext(c)
|
requestUser, err := uc.Service.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.View) {
|
|
||||||
|
if !requestUser.HasPrivilege(constants.Priviliges.View) {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle all users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle all users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := uc.Service.GetUsers(nil)
|
users, err := uc.Service.GetUsers(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error getting users in GetAllUsers", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
utils.RespondWithError(c, err, "Error getting all users", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,48 +73,45 @@ func (uc *UserController) GetAllUsers(c *gin.Context) {
|
|||||||
for i, user := range *users {
|
for i, user := range *users {
|
||||||
safeUsers[i] = user.Safe()
|
safeUsers[i] = user.Safe()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"users": users,
|
"users": safeUsers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) UpdateHandler(c *gin.Context) {
|
func (uc *UserController) UpdateHandler(c *gin.Context) {
|
||||||
// 1. Extract and validate the user ID from the route
|
// 1. Extract and validate the user ID from the route
|
||||||
requestUser, err := uc.ExtractUserFromContext(c)
|
requestUser, err := uc.Service.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user models.User
|
|
||||||
var updateData RegistrationData
|
var updateData RegistrationData
|
||||||
if err := c.ShouldBindJSON(&updateData); err != nil {
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
||||||
|
if updateData.User.Password != "" {
|
||||||
|
|
||||||
|
logger.Error.Printf("u.password: %#v", updateData.User.Password)
|
||||||
|
}
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user = updateData.User
|
user := updateData.User
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && user.ID != requestUser.ID {
|
if !requestUser.HasPrivilege(constants.Priviliges.Update) && user.ID != requestUser.ID {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
existingUser, err := uc.Service.GetUserByID(user.ID)
|
|
||||||
|
if requestUser.IsMember() {
|
||||||
|
existingUser, err := uc.Service.FromID(&user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error finding an existing user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
utils.RespondWithError(c, err, "Error finding an existing user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.MembershipID = existingUser.MembershipID
|
// deleting existing Users Password to prevent it from being recognized as changed in any case. (Incoming Password is empty if not changed)
|
||||||
user.Membership.ID = existingUser.Membership.ID
|
|
||||||
if existingUser.Licence != nil {
|
|
||||||
user.Licence.ID = existingUser.Licence.ID
|
|
||||||
}
|
|
||||||
user.LicenceID = existingUser.LicenceID
|
|
||||||
user.BankAccount.ID = existingUser.BankAccount.ID
|
|
||||||
user.BankAccountID = existingUser.BankAccountID
|
|
||||||
|
|
||||||
if requestUser.RoleID <= constants.Priviliges.View {
|
|
||||||
existingUser.Password = ""
|
existingUser.Password = ""
|
||||||
if err := utils.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
|
if err := validation.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
|
||||||
if err.Error() == "Not authorized" {
|
if err.Error() == "Not authorized" {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Trying to update unauthorized fields", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Trying to update unauthorized fields", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
} else {
|
} else {
|
||||||
@@ -120,30 +121,27 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedUser, err := uc.Service.UpdateUser(&user)
|
updatedUser, err := uc.Service.Update(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HandleUserUpdateError(c, err)
|
utils.HandleUserUpdateError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Printf("User %d updated successfully by user %d", updatedUser.ID, requestUser.ID)
|
logger.Info.Printf("User %v updated successfully by user %v", updatedUser.Email, requestUser.Email)
|
||||||
|
|
||||||
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()})
|
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) DeleteUser(c *gin.Context) {
|
func (uc *UserController) DeleteUser(c *gin.Context) {
|
||||||
|
|
||||||
requestUser, err := uc.ExtractUserFromContext(c)
|
requestUser, err := uc.Service.FromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error extracting user from context in DeleteUser", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
utils.RespondWithError(c, err, "Error extracting user from context in DeleteUser", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type deleteData struct {
|
type deleteData struct {
|
||||||
User struct {
|
ID uint `json:"id" binding:"required,numeric"`
|
||||||
ID uint `json:"id"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
} `json:"user"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data deleteData
|
var data deleteData
|
||||||
@@ -152,13 +150,13 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.HasPrivilige(requestUser, constants.Priviliges.Delete) && data.User.ID != requestUser.ID {
|
if !requestUser.HasPrivilege(constants.Priviliges.Delete) && data.ID != requestUser.ID {
|
||||||
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error.Printf("Deleting user: %v", data.User)
|
logger.Error.Printf("Deleting user: %v", data)
|
||||||
if err := uc.Service.DeleteUser(data.User.LastName, data.User.ID); err != nil {
|
if err := uc.Service.Delete(&data.ID); err != nil {
|
||||||
utils.HandleDeleteUserError(c, err)
|
utils.HandleDeleteUserError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -166,24 +164,6 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
|
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) ExtractUserFromContext(c *gin.Context) (*models.User, error) {
|
|
||||||
|
|
||||||
tokenString, err := c.Cookie("jwt")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, claims, err := middlewares.ExtractContentFrom(tokenString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jwtUserID := uint((*claims)["user_id"].(float64))
|
|
||||||
user, err := uc.Service.GetUserByID(jwtUserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UserController) LogoutHandler(c *gin.Context) {
|
func (uc *UserController) LogoutHandler(c *gin.Context) {
|
||||||
tokenString, err := c.Cookie("jwt")
|
tokenString, err := c.Cookie("jwt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -207,7 +187,7 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := uc.Service.GetUserByEmail(input.Email)
|
user, err := uc.Service.FromEmail(&input.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound,
|
utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound,
|
||||||
errors.Responses.Fields.Login,
|
errors.Responses.Fields.Login,
|
||||||
@@ -215,9 +195,9 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Status <= constants.DisabledStatus {
|
if !user.IsVerified() {
|
||||||
utils.RespondWithError(c, fmt.Errorf("User banned from login %v %v", user.FirstName, user.LastName),
|
utils.RespondWithError(c, fmt.Errorf("User banned from login or not verified %v %v", user.FirstName, user.LastName),
|
||||||
"Login Error; user is disabled",
|
"Login Error; user is disabled or not verified",
|
||||||
http.StatusNotAcceptable,
|
http.StatusNotAcceptable,
|
||||||
errors.Responses.Fields.Login,
|
errors.Responses.Fields.Login,
|
||||||
errors.Responses.Keys.UserDisabled)
|
errors.Responses.Keys.UserDisabled)
|
||||||
@@ -238,8 +218,10 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error.Printf("jwtsecret: %v", config.Auth.JWTSecret)
|
// "user_id": user.ID,
|
||||||
token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
|
// "role_id": user.RoleID,
|
||||||
|
claims := map[string]interface{}{"user_id": user.ID, "role_id": user.RoleID}
|
||||||
|
token, err := middlewares.GenerateToken(&config.Auth.JWTSecret, claims, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.JwtGenerationFailed)
|
utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.JwtGenerationFailed)
|
||||||
return
|
return
|
||||||
@@ -256,35 +238,38 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
func (uc *UserController) RegisterUser(c *gin.Context) {
|
func (uc *UserController) RegisterUser(c *gin.Context) {
|
||||||
|
|
||||||
var regData RegistrationData
|
var regData RegistrationData
|
||||||
logger.Error.Printf("registering user...")
|
|
||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
if err := c.ShouldBindJSON(®Data); err != nil {
|
||||||
|
logger.Error.Printf("Failed initial Binding: %#v", ®Data.User.Membership)
|
||||||
|
logger.Error.Printf("Failed initial Binding: %#v", ®Data.User.Membership.Subscription)
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Printf("Registering user %v", regData.User.Email)
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
regData.User.Membership.SubscriptionModel = *selectedModel
|
regData.User.Membership.Subscription = *selectedModel
|
||||||
if selectedModel.RequiredMembershipField != "" {
|
// Get Gin's binding validator engine with all registered validators
|
||||||
if err := validation.CheckParentMembershipID(regData.User.Membership); err != nil {
|
validate := binding.Validator.Engine().(*validator.Validate)
|
||||||
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't check parent membership id", http.StatusBadRequest, errors.Responses.Fields.ParentMemberShipID, errors.Responses.Keys.NotFound)
|
|
||||||
|
// Validate the populated user struct
|
||||||
|
if err := validate.Struct(regData.User); err != nil {
|
||||||
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
if regData.User.Membership.Subscription.Name == constants.SupporterSubscriptionName {
|
||||||
if regData.User.Membership.SubscriptionModel.Name == constants.SupporterSubscriptionModelName {
|
|
||||||
regData.User.RoleID = constants.Roles.Supporter
|
regData.User.RoleID = constants.Roles.Supporter
|
||||||
} else {
|
} else {
|
||||||
regData.User.RoleID = constants.Roles.Member
|
regData.User.RoleID = constants.Roles.Member
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register User
|
// Register User
|
||||||
id, token, err := uc.Service.RegisterUser(®Data.User)
|
id, token, err := uc.Service.Register(®Data.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") {
|
if strings.Contains(err.Error(), "UNIQUE constraint failed:") {
|
||||||
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.Email, errors.Responses.Keys.Duplicate)
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.Email, errors.Responses.Keys.Duplicate)
|
||||||
} else {
|
} else {
|
||||||
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
@@ -294,7 +279,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
regData.User.ID = id
|
regData.User.ID = id
|
||||||
|
|
||||||
// if this is a supporter don't send mails and he never did give any consent. So stop here
|
// if this is a supporter don't send mails and he never did give any consent. So stop here
|
||||||
if regData.User.RoleID == constants.Roles.Supporter {
|
if regData.User.IsSupporter() {
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, gin.H{
|
c.JSON(http.StatusCreated, gin.H{
|
||||||
"message": "Supporter Registration successuful",
|
"message": "Supporter Registration successuful",
|
||||||
@@ -310,14 +295,17 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
LastName: regData.User.LastName,
|
LastName: regData.User.LastName,
|
||||||
Email: regData.User.Email,
|
Email: regData.User.Email,
|
||||||
ConsentType: "TermsOfService",
|
ConsentType: "TermsOfService",
|
||||||
|
UserID: ®Data.User.ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
FirstName: regData.User.FirstName,
|
FirstName: regData.User.FirstName,
|
||||||
LastName: regData.User.LastName,
|
LastName: regData.User.LastName,
|
||||||
Email: regData.User.Email,
|
Email: regData.User.Email,
|
||||||
ConsentType: "Privacy",
|
ConsentType: "Privacy",
|
||||||
|
UserID: ®Data.User.ID,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, consent := range consents {
|
for _, consent := range consents {
|
||||||
_, err = uc.ConsentService.RegisterConsent(&consent)
|
_, err = uc.ConsentService.RegisterConsent(&consent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -326,6 +314,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Error.Printf("Sending Verification mail to user with id: %#v", id)
|
||||||
// Send notifications
|
// Send notifications
|
||||||
if err := uc.EmailService.SendVerificationEmail(®Data.User, &token); err != nil {
|
if err := uc.EmailService.SendVerificationEmail(®Data.User, &token); err != nil {
|
||||||
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't send verification email", http.StatusInternalServerError, errors.Responses.Fields.Email, errors.Responses.Keys.UndeliveredVerificationMail)
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't send verification email", http.StatusInternalServerError, errors.Responses.Fields.Email, errors.Responses.Keys.UndeliveredVerificationMail)
|
||||||
@@ -351,26 +340,36 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing token"})
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing token"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userIDint, err := strconv.Atoi(c.Param("id"))
|
||||||
verification, err := uc.Service.VerifyUser(&token, &constants.VerificationTypes.Email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't verify user"})
|
logger.Error.Println("Missing user ID to verify mail")
|
||||||
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing user"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userID := uint(userIDint)
|
||||||
user, err := uc.Service.GetUserByID(verification.UserID)
|
user, err := uc.Service.FromID(&userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Internal server error, couldn't verify user"})
|
logger.Error.Printf("Couldn't find user in verifyMailHandler: %#v", err)
|
||||||
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Status = constants.VerifiedStatus
|
user.Status = constants.VerifiedStatus
|
||||||
user.Verification = *verification
|
|
||||||
user.ID = verification.UserID
|
|
||||||
user.Password = ""
|
user.Password = ""
|
||||||
|
|
||||||
uc.Service.UpdateUser(user)
|
updatedUser, err := uc.Service.Update(user)
|
||||||
logger.Info.Printf("Verified User: %#v", user.Email)
|
if err != nil {
|
||||||
|
logger.Error.Printf("Failed to update user(%v) after verification: %v", user.Email, err)
|
||||||
|
c.HTML(http.StatusInternalServerError, "verification_error.html", gin.H{"ErrorMessage": "Internal server error, couldn't verify user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info.Printf("Verified User: %#v", updatedUser.Email)
|
||||||
|
|
||||||
uc.EmailService.SendWelcomeEmail(user)
|
uc.EmailService.SendWelcomeEmail(user)
|
||||||
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})
|
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -75,9 +76,8 @@ func testUserController(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// activate user for login
|
// activate user for login
|
||||||
database.DB.Model(&models.User{}).Where("email = ?", "john.doe@example.com").Update("status", constants.ActiveStatus)
|
database.DB.Model(&models.User{}).Where("email = ?", "john.doe@example.com").Update("status", constants.ActiveStatus)
|
||||||
loginEmail, loginCookie := testLoginHandler(t)
|
loginEmail := testLoginHandler(t)
|
||||||
logoutCookie := testCurrentUserHandler(t, loginEmail, loginCookie)
|
testCurrentUserHandler(t, loginEmail)
|
||||||
|
|
||||||
// creating a admin cookie
|
// creating a admin cookie
|
||||||
c, w, _ := GetMockedJSONContext([]byte(`{
|
c, w, _ := GetMockedJSONContext([]byte(`{
|
||||||
"email": "admin@example.com",
|
"email": "admin@example.com",
|
||||||
@@ -91,27 +91,27 @@ func testUserController(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "Login successful", response["message"])
|
assert.Equal(t, "Login successful", response["message"])
|
||||||
var adminCookie http.Cookie
|
|
||||||
for _, cookie := range w.Result().Cookies() {
|
for _, cookie := range w.Result().Cookies() {
|
||||||
if cookie.Name == "jwt" {
|
if cookie.Name == "jwt" {
|
||||||
adminCookie = *cookie
|
AdminCookie = cookie
|
||||||
|
|
||||||
tokenString := adminCookie.Value
|
tokenString := AdminCookie.Value
|
||||||
_, claims, err := middlewares.ExtractContentFrom(tokenString)
|
_, claims, err := middlewares.ExtractContentFrom(tokenString)
|
||||||
assert.NoError(t, err, "FAiled getting cookie string")
|
assert.NoError(t, err, "Failed getting cookie string")
|
||||||
jwtUserID := uint((*claims)["user_id"].(float64))
|
jwtUserID := uint((*claims)["user_id"].(float64))
|
||||||
user, err := Uc.Service.GetUserByID(jwtUserID)
|
user, err := Uc.Service.FromID(&jwtUserID)
|
||||||
assert.NoError(t, err, "FAiled getting cookie string")
|
assert.NoError(t, err, "Failed getting cookie string")
|
||||||
logger.Error.Printf("ADMIN USER: %#v", user)
|
logger.Error.Printf("ADMIN USER: %#v", user)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.NotEmpty(t, adminCookie)
|
assert.NotEmpty(t, AdminCookie)
|
||||||
testUpdateUser(t, loginCookie, adminCookie)
|
testUpdateUser(t)
|
||||||
testLogoutHandler(t, logoutCookie)
|
testLogoutHandler(t)
|
||||||
|
testCreatePasswordHandler(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
func testLogoutHandler(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -121,7 +121,7 @@ func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
|||||||
{
|
{
|
||||||
name: "Logout with valid cookie",
|
name: "Logout with valid cookie",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
@@ -179,9 +179,8 @@ func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
func testLoginHandler(t *testing.T) string {
|
||||||
// This test should run after the user registration test
|
// This test should run after the user registration test
|
||||||
var loginCookie http.Cookie
|
|
||||||
var loginInput loginInput
|
var loginInput loginInput
|
||||||
t.Run("LoginHandler", func(t *testing.T) {
|
t.Run("LoginHandler", func(t *testing.T) {
|
||||||
// Test cases
|
// Test cases
|
||||||
@@ -243,14 +242,14 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
|||||||
assert.Equal(t, "Login successful", response["message"])
|
assert.Equal(t, "Login successful", response["message"])
|
||||||
for _, cookie := range w.Result().Cookies() {
|
for _, cookie := range w.Result().Cookies() {
|
||||||
if cookie.Name == "jwt" {
|
if cookie.Name == "jwt" {
|
||||||
loginCookie = *cookie
|
MemberCookie = cookie
|
||||||
|
|
||||||
// tokenString := loginCookie.Value
|
tokenString := cookie.Value
|
||||||
// _, claims, err := middlewares.ExtractContentFrom(tokenString)
|
_, claims, err := middlewares.ExtractContentFrom(tokenString)
|
||||||
// assert.NoError(t, err, "FAiled getting cookie string")
|
assert.NoError(t, err, "FAiled getting cookie string")
|
||||||
// jwtUserID := uint((*claims)["user_id"].(float64))
|
jwtUserID := uint((*claims)["user_id"].(float64))
|
||||||
// user, err := Uc.Service.GetUserByID(jwtUserID)
|
_, err = Uc.Service.FromID(&jwtUserID)
|
||||||
// assert.NoError(t, err, "FAiled getting cookie string")
|
assert.NoError(t, err, "FAiled getting cookie string")
|
||||||
|
|
||||||
// logger.Error.Printf("cookie user: %#v", user)
|
// logger.Error.Printf("cookie user: %#v", user)
|
||||||
err = json.Unmarshal([]byte(tt.input), &loginInput)
|
err = json.Unmarshal([]byte(tt.input), &loginInput)
|
||||||
@@ -259,7 +258,7 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.NotEmpty(t, loginCookie)
|
assert.NotEmpty(t, MemberCookie)
|
||||||
} else {
|
} else {
|
||||||
assert.Contains(t, response, "errors")
|
assert.Contains(t, response, "errors")
|
||||||
assert.NotEmpty(t, response["errors"])
|
assert.NotEmpty(t, response["errors"])
|
||||||
@@ -268,10 +267,10 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return loginInput.Email, loginCookie
|
return loginInput.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Cookie) http.Cookie {
|
func testCurrentUserHandler(t *testing.T, loginEmail string) http.Cookie {
|
||||||
// This test should run after the user login test
|
// This test should run after the user login test
|
||||||
invalidCookie := http.Cookie{
|
invalidCookie := http.Cookie{
|
||||||
Name: "jwt",
|
Name: "jwt",
|
||||||
@@ -288,7 +287,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
{
|
{
|
||||||
name: "With valid cookie",
|
name: "With valid cookie",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
expectedUserMail: loginEmail,
|
expectedUserMail: loginEmail,
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
@@ -353,7 +352,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
if tt.expectedStatus == http.StatusOK {
|
if tt.expectedStatus == http.StatusOK {
|
||||||
var response struct {
|
var response struct {
|
||||||
User models.User `json:"user"`
|
User models.User `json:"user"`
|
||||||
Subscriptions []models.SubscriptionModel `json:"subscriptions"`
|
Subscriptions []models.Subscription `json:"subscriptions"`
|
||||||
}
|
}
|
||||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -368,7 +367,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
}
|
}
|
||||||
if tt.expectNewCookie {
|
if tt.expectNewCookie {
|
||||||
assert.NotNil(t, newCookie, "New cookie should be set for expired token")
|
assert.NotNil(t, newCookie, "New cookie should be set for expired token")
|
||||||
assert.NotEqual(t, loginCookie.Value, newCookie.Value, "Cookie value should be different")
|
assert.NotEqual(t, MemberCookie.Value, newCookie.Value, "Cookie value should be different")
|
||||||
assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
|
assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
|
||||||
} else {
|
} else {
|
||||||
assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
|
assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
|
||||||
@@ -394,7 +393,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return loginCookie
|
return *MemberCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
||||||
@@ -402,23 +401,30 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error in database ops: %#v", err)
|
return fmt.Errorf("Error in database ops: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if assert != (len(*users) != 0) {
|
if assert != (len(*users) != 0) {
|
||||||
return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users)
|
return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users)
|
||||||
}
|
}
|
||||||
if assert {
|
if assert {
|
||||||
user := (*users)[0]
|
user := (*users)[0]
|
||||||
// Check for mandate reference
|
// 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)
|
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
|
// Validate mandate reference format
|
||||||
expected := user.GenerateMandateReference()
|
expected := user.BankAccount.GenerateMandateReference(user.ID)
|
||||||
if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
|
if !strings.HasPrefix(user.BankAccount.MandateReference, expected) {
|
||||||
return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
|
return fmt.Errorf("Mandate reference is invalid. Expected: %s, Got: %s", expected, user.BankAccount.MandateReference)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Supoorter don't get mails
|
||||||
|
if user.IsSupporter() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//check for email delivery
|
//check for email delivery
|
||||||
messages := utils.SMTPGetMessages()
|
messages := utils.SMTPGetMessages()
|
||||||
for _, message := range messages {
|
for _, message := range messages {
|
||||||
@@ -453,18 +459,18 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cookie) {
|
func testUpdateUser(t *testing.T) {
|
||||||
|
|
||||||
invalidCookie := http.Cookie{
|
invalidCookie := http.Cookie{
|
||||||
Name: "jwt",
|
Name: "jwt",
|
||||||
Value: "invalid.token.here",
|
Value: "invalid.token.here",
|
||||||
}
|
}
|
||||||
// Get the user we just created
|
// Get the user we just created
|
||||||
users, err := Uc.Service.GetUsers(map[string]interface{}{"email": "john.doe@example.com"})
|
johnsMail := "john.doe@example.com"
|
||||||
if err != nil || len(*users) == 0 {
|
user, err := Uc.Service.FromEmail(&johnsMail)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("Failed to get test user: %v", err)
|
t.Fatalf("Failed to get test user: %v", err)
|
||||||
}
|
}
|
||||||
user := (*users)[0]
|
|
||||||
if user.Licence == nil {
|
if user.Licence == nil {
|
||||||
user.Licence = &models.Licence{
|
user.Licence = &models.Licence{
|
||||||
Number: "Z021AB37X13",
|
Number: "Z021AB37X13",
|
||||||
@@ -484,7 +490,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Valid Admin Update",
|
name: "Valid Admin Update",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&adminCookie)
|
req.AddCookie(AdminCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -513,7 +519,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Invalid Email Update",
|
name: "Invalid Email Update",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -531,7 +537,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "admin may change licence number",
|
name: "admin may change licence number",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&adminCookie)
|
req.AddCookie(AdminCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -545,7 +551,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Change phone number",
|
name: "Change phone number",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -559,7 +565,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Add category",
|
name: "Add category",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -570,14 +576,14 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||||
category, err := licenceRepo.FindCategoryByName("B")
|
category, err := licenceRepo.FindCategoryByName("B")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
u.Licence.Categories = []models.Category{category}
|
u.Licence.Categories = []*models.Category{&category}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Delete 1 and add 1 category",
|
name: "Delete 1 and add 1 category",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -589,14 +595,14 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
category, err := licenceRepo.FindCategoryByName("A")
|
category, err := licenceRepo.FindCategoryByName("A")
|
||||||
category2, err := licenceRepo.FindCategoryByName("BE")
|
category2, err := licenceRepo.FindCategoryByName("BE")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
u.Licence.Categories = []models.Category{category, category2}
|
u.Licence.Categories = []*models.Category{&category, &category2}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Delete 1 category",
|
name: "Delete 1 category",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -607,14 +613,14 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||||
category, err := licenceRepo.FindCategoryByName("A")
|
category, err := licenceRepo.FindCategoryByName("A")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
u.Licence.Categories = []models.Category{category}
|
u.Licence.Categories = []*models.Category{&category}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Delete all categories",
|
name: "Delete all categories",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -622,14 +628,14 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.Licence.Number = "B072RRE2I50"
|
u.Licence.Number = "B072RRE2I50"
|
||||||
u.Licence.Categories = []models.Category{}
|
u.Licence.Categories = []*models.Category{}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "User ID mismatch while not admin",
|
name: "User ID mismatch while not admin",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -648,7 +654,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Password Update low entropy should fail",
|
name: "Password Update low entropy should fail",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
@@ -665,7 +671,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Password Update",
|
name: "Password Update",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
@@ -683,10 +689,24 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
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",
|
name: "Admin Password Update",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&adminCookie)
|
req.AddCookie(AdminCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
@@ -699,7 +719,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
{
|
{
|
||||||
name: "Non-existent User",
|
name: "Non-existent User",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(MemberCookie)
|
||||||
},
|
},
|
||||||
updateFunc: func(u *models.User) {
|
updateFunc: func(u *models.User) {
|
||||||
u.Password = ""
|
u.Password = ""
|
||||||
@@ -718,7 +738,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
logger.Error.Print("==============================================================")
|
logger.Error.Print("==============================================================")
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Create a copy of the user and apply the updates
|
// Create a copy of the user and apply the updates
|
||||||
updatedUser := user
|
updatedUser := *user
|
||||||
// logger.Error.Printf("users licence to be updated: %+v", user.Licence)
|
// logger.Error.Printf("users licence to be updated: %+v", user.Licence)
|
||||||
tt.updateFunc(&updatedUser)
|
tt.updateFunc(&updatedUser)
|
||||||
|
|
||||||
@@ -783,13 +803,17 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
assert.Equal(t, "User updated successfully", message)
|
assert.Equal(t, "User updated successfully", message)
|
||||||
|
|
||||||
// Verify the update in the database
|
// Verify the update in the database
|
||||||
updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
|
updatedUserFromDB, err := Uc.Service.FromID(&user.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
if updatedUser.Password == "" {
|
if updatedUser.Password == "" {
|
||||||
assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
|
assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
|
||||||
} else {
|
} 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 = ""
|
updatedUserFromDB.Password = ""
|
||||||
@@ -801,11 +825,9 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch")
|
assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch")
|
||||||
assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch")
|
assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch")
|
||||||
assert.Equal(t, updatedUser.Notes, updatedUserFromDB.Notes, "Notes mismatch")
|
assert.Equal(t, updatedUser.Notes, updatedUserFromDB.Notes, "Notes mismatch")
|
||||||
assert.Equal(t, updatedUser.ProfilePicture, updatedUserFromDB.ProfilePicture, "ProfilePicture mismatch")
|
|
||||||
assert.Equal(t, updatedUser.Address, updatedUserFromDB.Address, "Address mismatch")
|
assert.Equal(t, updatedUser.Address, updatedUserFromDB.Address, "Address mismatch")
|
||||||
assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch")
|
assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch")
|
||||||
assert.Equal(t, updatedUser.City, updatedUserFromDB.City, "City mismatch")
|
assert.Equal(t, updatedUser.City, updatedUserFromDB.City, "City mismatch")
|
||||||
assert.Equal(t, updatedUser.PaymentStatus, updatedUserFromDB.PaymentStatus, "PaymentStatus mismatch")
|
|
||||||
assert.Equal(t, updatedUser.Status, updatedUserFromDB.Status, "Status mismatch")
|
assert.Equal(t, updatedUser.Status, updatedUserFromDB.Status, "Status mismatch")
|
||||||
assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch")
|
assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch")
|
||||||
|
|
||||||
@@ -819,17 +841,32 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie, adminCookie http.Cook
|
|||||||
assert.Equal(t, updatedUser.Membership.StartDate, updatedUserFromDB.Membership.StartDate, "Membership.StartDate mismatch")
|
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.EndDate, updatedUserFromDB.Membership.EndDate, "Membership.EndDate mismatch")
|
||||||
assert.Equal(t, updatedUser.Membership.Status, updatedUserFromDB.Membership.Status, "Membership.Status 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")
|
assert.Equal(t, updatedUser.Membership.ParentMembershipID, updatedUserFromDB.Membership.ParentMembershipID, "Membership.ParentMembershipID mismatch")
|
||||||
|
|
||||||
|
if updatedUser.Licence == nil {
|
||||||
|
assert.Nil(t, updatedUserFromDB.Licence, "database licence of user is not nil, but user.licence is nil")
|
||||||
|
} else {
|
||||||
|
logger.Error.Printf("updatedUser licence: %#v", updatedUser.Licence)
|
||||||
|
logger.Error.Printf("dbUser licence: %#v", updatedUserFromDB.Licence)
|
||||||
assert.Equal(t, updatedUser.Licence.Status, updatedUserFromDB.Licence.Status, "Licence.Status mismatch")
|
assert.Equal(t, updatedUser.Licence.Status, updatedUserFromDB.Licence.Status, "Licence.Status mismatch")
|
||||||
assert.Equal(t, updatedUser.Licence.Number, updatedUserFromDB.Licence.Number, "Licence.Number mismatch")
|
assert.Equal(t, updatedUser.Licence.Number, updatedUserFromDB.Licence.Number, "Licence.Number mismatch")
|
||||||
assert.Equal(t, updatedUser.Licence.IssuedDate, updatedUserFromDB.Licence.IssuedDate, "Licence.IssuedDate mismatch")
|
assert.Equal(t, updatedUser.Licence.IssuedDate, updatedUserFromDB.Licence.IssuedDate, "Licence.IssuedDate mismatch")
|
||||||
assert.Equal(t, updatedUser.Licence.ExpirationDate, updatedUserFromDB.Licence.ExpirationDate, "Licence.ExpirationDate mismatch")
|
assert.Equal(t, updatedUser.Licence.ExpirationDate, updatedUserFromDB.Licence.ExpirationDate, "Licence.ExpirationDate mismatch")
|
||||||
assert.Equal(t, updatedUser.Licence.IssuingCountry, updatedUserFromDB.Licence.IssuingCountry, "Licence.IssuingCountry mismatch")
|
assert.Equal(t, updatedUser.Licence.IssuingCountry, updatedUserFromDB.Licence.IssuingCountry, "Licence.IssuingCountry mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
// For slices or more complex nested structures, you might want to use deep equality checks
|
if len(updatedUser.Consents) > 0 {
|
||||||
assert.ElementsMatch(t, updatedUser.Consents, updatedUserFromDB.Consents, "Consents mismatch")
|
for i := range updatedUser.Consents {
|
||||||
|
assert.Equal(t, updatedUser.Consents[i].ConsentType, updatedUserFromDB.Consents[i].ConsentType, "ConsentType mismatch at index %d", i)
|
||||||
|
assert.Equal(t, updatedUser.Consents[i].Email, updatedUserFromDB.Consents[i].Email, "ConsentEmail mismatch at index %d", i)
|
||||||
|
assert.Equal(t, updatedUser.Consents[i].FirstName, updatedUserFromDB.Consents[i].FirstName, "ConsentFirstName mismatch at index %d", i)
|
||||||
|
assert.Equal(t, updatedUser.Consents[i].LastName, updatedUserFromDB.Consents[i].LastName, "ConsentLastName mismatch at index %d", i)
|
||||||
|
assert.Equal(t, updatedUser.Consents[i].UserID, updatedUserFromDB.Consents[i].UserID, "Consent UserId mismatch at index %d", i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Emptyf(t, updatedUserFromDB.Licence.Categories, "Categories aren't empty when they should")
|
||||||
|
}
|
||||||
if len(updatedUser.Licence.Categories) > 0 {
|
if len(updatedUser.Licence.Categories) > 0 {
|
||||||
for i := range updatedUser.Licence.Categories {
|
for i := range updatedUser.Licence.Categories {
|
||||||
assert.Equal(t, updatedUser.Licence.Categories[i].Name, updatedUserFromDB.Licence.Categories[i].Name, "Category Category mismatch at index %d", i)
|
assert.Equal(t, updatedUser.Licence.Categories[i].Name, updatedUserFromDB.Licence.Categories[i].Name, "Category Category mismatch at index %d", i)
|
||||||
@@ -855,11 +892,11 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error {
|
|||||||
if !strings.Contains(message.Body, user.FirstName) {
|
if !strings.Contains(message.Body, user.FirstName) {
|
||||||
return fmt.Errorf("User first name(%v) has not been rendered in registration mail.", 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)) {
|
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.SubscriptionModel.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)) {
|
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.SubscriptionModel.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) {
|
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
|
||||||
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
||||||
@@ -891,11 +928,11 @@ func checkRegistrationMail(message *utils.Email, user *models.User) error {
|
|||||||
if !strings.Contains(message.Body, user.FirstName+" "+user.LastName) {
|
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)
|
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)) {
|
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.SubscriptionModel.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)) {
|
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.SubscriptionModel.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) {
|
if user.Company != "" && !strings.Contains(message.Body, user.Company) {
|
||||||
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
return fmt.Errorf("Users Company(%v) has not been rendered in registration mail.", user.Company)
|
||||||
@@ -935,15 +972,19 @@ func checkVerificationMail(message *utils.Email, user *models.User) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
|
return fmt.Errorf("Error parsing verification URL: %#v", err.Error())
|
||||||
}
|
}
|
||||||
if !strings.Contains(verificationURL, user.Verification.VerificationToken) {
|
v, err := user.FindVerification(constants.VerificationTypes.Email)
|
||||||
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL)
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error getting verification token: %v", err.Error())
|
||||||
|
}
|
||||||
|
if !strings.Contains(verificationURL, v.VerificationToken) {
|
||||||
|
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", v.VerificationToken, verificationURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(message.Body, config.Site.BaseURL) {
|
if !strings.Contains(message.Body, config.Site.BaseURL) {
|
||||||
return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.Site.BaseURL)
|
return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.Site.BaseURL)
|
||||||
}
|
}
|
||||||
// open the provided link:
|
// open the provided link:
|
||||||
if err := verifyMail(verificationURL); err != nil {
|
if err := verifyMail(verificationURL, user.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
messages := utils.SMTPGetMessages()
|
messages := utils.SMTPGetMessages()
|
||||||
@@ -960,12 +1001,14 @@ func checkVerificationMail(message *utils.Email, user *models.User) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyMail(verificationURL string) error {
|
func verifyMail(verificationURL string, user_id uint) error {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.LoadHTMLGlob(filepath.Join(config.Templates.HTMLPath, "*"))
|
router.LoadHTMLGlob(filepath.Join(config.Templates.HTMLPath, "*"))
|
||||||
|
|
||||||
router.GET("api/users/verify", Uc.VerifyMailHandler)
|
expectedUrl := fmt.Sprintf("/api/users/verify/%v", user_id)
|
||||||
|
log.Printf("Expected URL: %v", expectedUrl)
|
||||||
|
router.GET("/api/users/verify/:id", Uc.VerifyMailHandler)
|
||||||
wv := httptest.NewRecorder()
|
wv := httptest.NewRecorder()
|
||||||
cv, _ := gin.CreateTestContext(wv)
|
cv, _ := gin.CreateTestContext(wv)
|
||||||
var err error
|
var err error
|
||||||
@@ -1108,8 +1151,9 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.BankAccount.IBAN = "DE1234234123134"
|
user.BankAccount.IBAN = "DE1234234123134"
|
||||||
user.RoleID = 0
|
user.RoleID = constants.Roles.Supporter
|
||||||
user.Email = "john.supporter@example.com"
|
user.Email = "john.supporter@example.com"
|
||||||
|
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1120,8 +1164,9 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
Assert: true,
|
Assert: true,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.BankAccount.IBAN = ""
|
user.BankAccount.IBAN = ""
|
||||||
user.RoleID = 0
|
user.RoleID = constants.Roles.Supporter
|
||||||
user.Email = "john.supporter@example.com"
|
user.Email = "john.supporter@example.com"
|
||||||
|
user.Membership.Subscription.Name = constants.SupporterSubscriptionName
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1131,7 +1176,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.Membership.SubscriptionModel.Name = ""
|
user.Membership.Subscription.Name = ""
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1141,7 +1186,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.Membership.SubscriptionModel.Name = "NOTEXISTENTPLAN"
|
user.Membership.Subscription.Name = "NOTEXISTENTPLAN"
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1180,7 +1225,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.Email = "john.junior.doe@example.com"
|
user.Email = "john.junior.doe@example.com"
|
||||||
user.Membership.SubscriptionModel.Name = "additional"
|
user.Membership.Subscription.Name = "additional"
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1192,7 +1237,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.Email = "john.junior.doe@example.com"
|
user.Email = "john.junior.doe@example.com"
|
||||||
user.Membership.ParentMembershipID = 200
|
user.Membership.ParentMembershipID = 200
|
||||||
user.Membership.SubscriptionModel.Name = "additional"
|
user.Membership.Subscription.Name = "additional"
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1204,7 +1249,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
user.Email = "john.junior.doe@example.com"
|
user.Email = "john.junior.doe@example.com"
|
||||||
user.Membership.ParentMembershipID = 1
|
user.Membership.ParentMembershipID = 1
|
||||||
user.Membership.SubscriptionModel.Name = "additional"
|
user.Membership.Subscription.Name = "additional"
|
||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
@@ -1235,35 +1280,35 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
// return user
|
// return user
|
||||||
// })),
|
// })),
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// Name: "empty driverslicence number, should fail",
|
Name: "empty driverslicence number, should fail",
|
||||||
// WantResponse: http.StatusBadRequest,
|
WantResponse: http.StatusBadRequest,
|
||||||
// WantDBData: map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
|
||||||
// Assert: false,
|
Assert: false,
|
||||||
// Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
// user.Email = "john.wronglicence.doe@example.com"
|
user.Email = "john.wronglicence.doe@example.com"
|
||||||
// user.Licence = &models.Licence{
|
user.Licence = &models.Licence{
|
||||||
// Number: "",
|
Number: "",
|
||||||
// ExpirationDate: time.Now().AddDate(1, 0, 0),
|
ExpirationDate: time.Now().AddDate(1, 0, 0),
|
||||||
// IssuedDate: time.Now().AddDate(-1, 0, 0),
|
IssuedDate: time.Now().AddDate(-1, 0, 0),
|
||||||
// }
|
}
|
||||||
// return user
|
return user
|
||||||
// })),
|
})),
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// Name: "Correct Licence number, should pass",
|
Name: "Correct Licence number, should pass",
|
||||||
// WantResponse: http.StatusCreated,
|
WantResponse: http.StatusCreated,
|
||||||
// WantDBData: map[string]interface{}{"email": "john.correctLicenceNumber@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.correctlicencenumber@example.com"},
|
||||||
// Assert: true,
|
Assert: true,
|
||||||
// Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
// user.Email = "john.correctLicenceNumber@example.com"
|
user.Email = "john.correctLicenceNumber@example.com"
|
||||||
// user.Licence = &models.Licence{
|
user.Licence = &models.Licence{
|
||||||
// Number: "B072RRE2I55",
|
Number: "B072RRE2I55",
|
||||||
// ExpirationDate: time.Now().AddDate(1, 0, 0),
|
ExpirationDate: time.Now().AddDate(1, 0, 0),
|
||||||
// IssuedDate: time.Now().AddDate(-1, 0, 0),
|
IssuedDate: time.Now().AddDate(-1, 0, 0),
|
||||||
// }
|
}
|
||||||
// return user
|
return user
|
||||||
// })),
|
})),
|
||||||
// },
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,37 +6,70 @@ import (
|
|||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
|
|
||||||
func Open(dbPath string, adminMail string) error {
|
func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
|
||||||
|
// Add foreign key support and WAL journal mode to DSN
|
||||||
|
dsn := fmt.Sprintf("%s?_foreign_keys=1&_journal_mode=WAL", dbPath)
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
|
||||||
|
// Enable PrepareStmt for better performance
|
||||||
|
PrepareStmt: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("failed to connect database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify foreign key support is enabled
|
||||||
|
var foreignKeyEnabled int
|
||||||
|
if err := db.Raw("PRAGMA foreign_keys").Scan(&foreignKeyEnabled).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("foreign key check failed: %w", err)
|
||||||
|
}
|
||||||
|
if foreignKeyEnabled != 1 {
|
||||||
|
return nil, errors.New("SQLite foreign key constraints not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
db = db.Debug()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure connection pool
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get DB instance: %w", err)
|
||||||
|
}
|
||||||
|
sqlDB.SetMaxOpenConns(1) // Required for SQLite in production
|
||||||
|
sqlDB.SetMaxIdleConns(1)
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
|
||||||
|
db.Exec("PRAGMA foreign_keys = OFF;")
|
||||||
if err := db.AutoMigrate(
|
if err := db.AutoMigrate(
|
||||||
&models.User{},
|
&models.Subscription{},
|
||||||
&models.SubscriptionModel{},
|
|
||||||
&models.Membership{},
|
&models.Membership{},
|
||||||
&models.Consent{},
|
&models.Consent{},
|
||||||
&models.Verification{},
|
&models.Verification{},
|
||||||
|
&models.BankAccount{},
|
||||||
&models.Licence{},
|
&models.Licence{},
|
||||||
&models.Category{},
|
&models.Category{},
|
||||||
&models.BankAccount{}); err != nil {
|
&models.Car{},
|
||||||
logger.Error.Fatalf("Couldn't create database: %v", err)
|
&models.Location{},
|
||||||
return err
|
&models.Damage{},
|
||||||
|
// &models.Insurance{},
|
||||||
|
// &models.User{},
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
||||||
}
|
}
|
||||||
DB = db
|
db.Exec("PRAGMA foreign_keys = ON;")
|
||||||
|
|
||||||
logger.Info.Print("Opened DB")
|
logger.Info.Print("Opened DB")
|
||||||
|
DB = db
|
||||||
var categoriesCount int64
|
var categoriesCount int64
|
||||||
db.Model(&models.Category{}).Count(&categoriesCount)
|
db.Model(&models.Category{}).Count(&categoriesCount)
|
||||||
if categoriesCount == 0 {
|
if categoriesCount == 0 {
|
||||||
@@ -44,25 +77,25 @@ func Open(dbPath string, adminMail string) error {
|
|||||||
for _, model := range categories {
|
for _, model := range categories {
|
||||||
result := db.Create(&model)
|
result := db.Create(&model)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscriptionsCount int64
|
var subscriptionsCount int64
|
||||||
db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount)
|
db.Model(&models.Subscription{}).Count(&subscriptionsCount)
|
||||||
subscriptionModels := createSubscriptionModels()
|
subscriptions := createSubscriptions()
|
||||||
for _, model := range subscriptionModels {
|
for _, model := range subscriptions {
|
||||||
var exists int64
|
var exists int64
|
||||||
db.
|
db.
|
||||||
Model(&models.SubscriptionModel{}).
|
Model(&models.Subscription{}).
|
||||||
Where("name = ?", model.Name).
|
Where("name = ?", model.Name).
|
||||||
Count(&exists)
|
Count(&exists)
|
||||||
logger.Error.Printf("looked for model.name %v and found %v", model.Name, exists)
|
logger.Error.Printf("looked for model.name %v and found %v", model.Name, exists)
|
||||||
if exists == 0 {
|
if exists == 0 {
|
||||||
result := db.Create(&model)
|
result := db.Create(&model)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,28 +103,25 @@ func Open(dbPath string, adminMail string) error {
|
|||||||
var userCount int64
|
var userCount int64
|
||||||
db.Model(&models.User{}).Count(&userCount)
|
db.Model(&models.User{}).Count(&userCount)
|
||||||
if userCount == 0 {
|
if userCount == 0 {
|
||||||
var createdModel models.SubscriptionModel
|
var createdModel models.Subscription
|
||||||
if err := db.First(&createdModel).Error; err != nil {
|
if err := db.First(&createdModel).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
admin, err := createAdmin(adminMail, createdModel.ID)
|
admin, err := createAdmin(adminMail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
|
||||||
result := db.Session(&gorm.Session{FullSaveAssociations: true}).Create(&admin)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
}
|
||||||
|
admin.Create(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSubscriptionModels() []models.SubscriptionModel {
|
func createSubscriptions() []models.Subscription {
|
||||||
return []models.SubscriptionModel{
|
return []models.Subscription{
|
||||||
{
|
{
|
||||||
Name: constants.SupporterSubscriptionModelName,
|
Name: constants.SupporterSubscriptionName,
|
||||||
Details: "Dieses Modell ist für Sponsoren und Nichtmitglieder, die keinen Vereinsmitglied sind.",
|
Details: "Dieses Modell ist für Sponsoren und Nichtmitglieder, die keinen Vereinsmitglied sind.",
|
||||||
HourlyRate: 999,
|
HourlyRate: 999,
|
||||||
MonthlyFee: 0,
|
MonthlyFee: 0,
|
||||||
@@ -122,7 +152,7 @@ func createLicenceCategories() []models.Category {
|
|||||||
|
|
||||||
// TODO: Landing page to create an admin
|
// TODO: Landing page to create an admin
|
||||||
|
|
||||||
func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error) {
|
func createAdmin(userMail string) (*models.User, error) {
|
||||||
passwordBytes := make([]byte, 12)
|
passwordBytes := make([]byte, 12)
|
||||||
_, err := rand.Read(passwordBytes)
|
_, err := rand.Read(passwordBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,46 +162,39 @@ func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error
|
|||||||
// Encode into a URL-safe base64 string
|
// Encode into a URL-safe base64 string
|
||||||
password := base64.URLEncoding.EncodeToString(passwordBytes)[:12]
|
password := base64.URLEncoding.EncodeToString(passwordBytes)[:12]
|
||||||
|
|
||||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Error.Print("==============================================================")
|
logger.Error.Print("==============================================================")
|
||||||
logger.Error.Printf("Admin Email: %v", userMail)
|
logger.Error.Printf("Admin Email: %v", userMail)
|
||||||
logger.Error.Printf("Admin Password: %v", password)
|
logger.Error.Printf("Admin Password: %v", password)
|
||||||
logger.Error.Print("==============================================================")
|
logger.Error.Print("==============================================================")
|
||||||
|
|
||||||
return &models.User{
|
return &models.User{
|
||||||
FirstName: "ad",
|
FirstName: "Ad",
|
||||||
LastName: "min",
|
LastName: "Min",
|
||||||
DateOfBirth: time.Now().AddDate(-20, 0, 0),
|
DateOfBirth: time.Now().AddDate(-20, 0, 0),
|
||||||
Password: hash,
|
Password: password,
|
||||||
Address: "Downhill 4",
|
Company: "",
|
||||||
ZipCode: "99999",
|
Address: "",
|
||||||
City: "TechTown",
|
ZipCode: "",
|
||||||
Phone: "0123455678",
|
City: "",
|
||||||
|
Phone: "",
|
||||||
|
Notes: "",
|
||||||
Email: userMail,
|
Email: userMail,
|
||||||
Status: constants.ActiveStatus,
|
Status: constants.ActiveStatus,
|
||||||
RoleID: constants.Roles.Admin,
|
RoleID: constants.Roles.Admin,
|
||||||
Membership: models.Membership{
|
Consents: nil,
|
||||||
Status: constants.DisabledStatus,
|
Verifications: nil,
|
||||||
StartDate: time.Now(),
|
Membership: nil,
|
||||||
SubscriptionModelID: subscriptionModelID,
|
BankAccount: nil,
|
||||||
},
|
Licence: nil,
|
||||||
BankAccount: models.BankAccount{},
|
|
||||||
Licence: &models.Licence{
|
|
||||||
Status: constants.UnverifiedStatus,
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
//"DE49700500000008447644", //fake
|
//"DE49700500000008447644", //fake
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() error {
|
func Close(db *gorm.DB) error {
|
||||||
logger.Info.Print("Closing DB")
|
logger.Info.Print("Closing DB")
|
||||||
db, err := DB.DB()
|
database, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return db.Close()
|
return database.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package middlewares
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/internal/config"
|
"GoMembership/internal/config"
|
||||||
"GoMembership/internal/models"
|
|
||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
customerrors "GoMembership/pkg/errors"
|
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -34,26 +32,43 @@ func verifyAndRenewToken(tokenString string) (string, uint, error) {
|
|||||||
return "", 0, fmt.Errorf("Authorization token is required")
|
return "", 0, fmt.Errorf("Authorization token is required")
|
||||||
}
|
}
|
||||||
token, claims, err := ExtractContentFrom(tokenString)
|
token, claims, err := ExtractContentFrom(tokenString)
|
||||||
if err != nil {
|
|
||||||
|
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
logger.Error.Printf("Couldn't parse JWT token String: %v", err)
|
logger.Error.Printf("Couldn't parse JWT token String: %v", err)
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
sessionID := (*claims)["session_id"].(string)
|
|
||||||
userID := uint((*claims)["user_id"].(float64))
|
if token.Valid {
|
||||||
roleID := int8((*claims)["role_id"].(float64))
|
// token is valid, so we can return the old tokenString
|
||||||
|
return tokenString, uint((*claims)["user_id"].(float64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token is expired but valid
|
||||||
|
sessionID, ok := (*claims)["session_id"].(string)
|
||||||
|
if !ok || sessionID == "" {
|
||||||
|
return "", 0, fmt.Errorf("invalid session ID")
|
||||||
|
}
|
||||||
|
id, ok := (*claims)["user_id"]
|
||||||
|
if !ok {
|
||||||
|
return "", 0, fmt.Errorf("missing user_id claim")
|
||||||
|
}
|
||||||
|
userID := uint(id.(float64))
|
||||||
|
|
||||||
|
id, ok = (*claims)["role_id"]
|
||||||
|
if !ok {
|
||||||
|
return "", 0, fmt.Errorf("missing role_id claim")
|
||||||
|
}
|
||||||
|
roleID := int8(id.(float64))
|
||||||
|
|
||||||
session, ok := sessions[sessionID]
|
session, ok := sessions[sessionID]
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Error.Printf("session not found")
|
logger.Error.Printf("session not found")
|
||||||
return "", 0, fmt.Errorf("session not found")
|
return "", 0, fmt.Errorf("session not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if userID != session.UserID {
|
if userID != session.UserID {
|
||||||
return "", 0, fmt.Errorf("Cookie has been altered, aborting..")
|
return "", 0, fmt.Errorf("Cookie has been altered, aborting..")
|
||||||
}
|
}
|
||||||
if token.Valid {
|
|
||||||
// token is valid, so we can return the old tokenString
|
|
||||||
return tokenString, session.UserID, customerrors.ErrValidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().After(sessions[sessionID].ExpiresAt) {
|
if time.Now().After(sessions[sessionID].ExpiresAt) {
|
||||||
delete(sessions, sessionID)
|
delete(sessions, sessionID)
|
||||||
@@ -64,8 +79,8 @@ func verifyAndRenewToken(tokenString string) (string, uint, error) {
|
|||||||
|
|
||||||
logger.Error.Printf("Session still valid generating new token")
|
logger.Error.Printf("Session still valid generating new token")
|
||||||
// Session is still valid, generate a new token
|
// Session is still valid, generate a new token
|
||||||
user := models.User{ID: userID, RoleID: roleID}
|
user := map[string]interface{}{"user_id": userID, "role_id": roleID}
|
||||||
newTokenString, err := GenerateToken(config.Auth.JWTSecret, &user, sessionID)
|
newTokenString, err := GenerateToken(&config.Auth.JWTSecret, user, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
@@ -89,11 +104,6 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
|
|
||||||
newToken, userID, err := verifyAndRenewToken(tokenString)
|
newToken, userID, err := verifyAndRenewToken(tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == customerrors.ErrValidToken {
|
|
||||||
c.Set("user_id", uint(userID))
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Error.Printf("Token(%v) is invalid: %v\n", tokenString, err)
|
logger.Error.Printf("Token(%v) is invalid: %v\n", tokenString, err)
|
||||||
c.JSON(http.StatusUnauthorized,
|
c.JSON(http.StatusUnauthorized,
|
||||||
gin.H{"errors": []gin.H{{
|
gin.H{"errors": []gin.H{{
|
||||||
@@ -104,24 +114,30 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newToken != tokenString {
|
||||||
utils.SetCookie(c, newToken)
|
utils.SetCookie(c, newToken)
|
||||||
|
}
|
||||||
c.Set("user_id", uint(userID))
|
c.Set("user_id", uint(userID))
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateToken(jwtKey string, user *models.User, sessionID string) (string, error) {
|
// GenerateToken generates a new JWT token with the given claims and session ID.
|
||||||
|
// "user_id": user.ID, "role_id": user.RoleID
|
||||||
|
func GenerateToken(jwtKey *string, claims map[string]interface{}, sessionID string) (string, error) {
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
sessionID = uuid.New().String()
|
sessionID = uuid.New().String()
|
||||||
}
|
}
|
||||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
claims["session_id"] = sessionID
|
||||||
"user_id": user.ID,
|
claims["exp"] = time.Now().Add(time.Minute * 1).Unix() // Token expires in 10 Minutes
|
||||||
"role_id": user.RoleID,
|
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims(claims))
|
||||||
"session_id": sessionID,
|
|
||||||
"exp": time.Now().Add(time.Minute * 1).Unix(), // Token expires in 10 Minutes
|
userID, ok := claims["user_id"].(uint)
|
||||||
})
|
if !ok {
|
||||||
UpdateSession(sessionID, user.ID)
|
return "", fmt.Errorf("invalid user_id in claims")
|
||||||
return token.SignedString([]byte(jwtKey))
|
}
|
||||||
|
UpdateSession(sessionID, userID)
|
||||||
|
return token.SignedString([]byte(*jwtKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractContentFrom(tokenString string) (*jwt.Token, *jwt.MapClaims, error) {
|
func ExtractContentFrom(tokenString string) (*jwt.Token, *jwt.MapClaims, error) {
|
||||||
@@ -130,23 +146,33 @@ func ExtractContentFrom(tokenString string) (*jwt.Token, *jwt.MapClaims, error)
|
|||||||
return []byte(config.Auth.JWTSecret), nil
|
return []byte(config.Auth.JWTSecret), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if !errors.Is(err, jwt.ErrTokenExpired) && err != nil {
|
// Handle parsing errors (excluding expiration error)
|
||||||
logger.Error.Printf("Error during token(%v) parsing: %#v", tokenString, err)
|
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
logger.Error.Printf("Error parsing token: %v", err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token is expired, check if session is still valid
|
// Ensure token is not nil (e.g., malformed tokens)
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
if token == nil {
|
||||||
if !ok {
|
logger.Error.Print("Token is nil after parsing")
|
||||||
logger.Error.Printf("Invalid Token Claims")
|
return nil, nil, fmt.Errorf("invalid token")
|
||||||
return nil, nil, fmt.Errorf("invalid token claims")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract and validate claims
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Error.Printf("invalid session_id in token")
|
logger.Error.Print("Invalid token claims structure")
|
||||||
return nil, nil, fmt.Errorf("invalid session_id in token")
|
return nil, nil, fmt.Errorf("invalid token claims format")
|
||||||
}
|
}
|
||||||
return token, &claims, nil
|
|
||||||
|
// Validate required session_id claim
|
||||||
|
if _, exists := claims["session_id"]; !exists {
|
||||||
|
logger.Error.Print("Missing session_id in token claims")
|
||||||
|
return nil, nil, fmt.Errorf("missing session_id claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return token, claims, and original error (might be expiration)
|
||||||
|
return token, &claims, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateSession(sessionID string, userID uint) {
|
func UpdateSession(sessionID string, userID uint) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package middlewares
|
|||||||
import (
|
import (
|
||||||
"GoMembership/internal/config"
|
"GoMembership/internal/config"
|
||||||
"GoMembership/internal/constants"
|
"GoMembership/internal/constants"
|
||||||
"GoMembership/internal/models"
|
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
@@ -56,8 +55,11 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Valid Token",
|
name: "Valid Token",
|
||||||
setupAuth: func(r *http.Request) {
|
setupAuth: func(r *http.Request) {
|
||||||
user := models.User{ID: 123, RoleID: constants.Roles.Member}
|
claims := map[string]interface{}{"user_id": uint(123), "role_id": constants.Roles.Member}
|
||||||
token, _ := GenerateToken(config.Auth.JWTSecret, &user, "")
|
token, err := GenerateToken(&config.Auth.JWTSecret, claims, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
r.AddCookie(&http.Cookie{Name: "jwt", Value: token})
|
r.AddCookie(&http.Cookie{Name: "jwt", Value: token})
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
@@ -82,7 +84,7 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
setupAuth: func(r *http.Request) {
|
setupAuth: func(r *http.Request) {
|
||||||
sessionID := "test-session"
|
sessionID := "test-session"
|
||||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||||
"user_id": 123,
|
"user_id": uint(123),
|
||||||
"role_id": constants.Roles.Member,
|
"role_id": constants.Roles.Member,
|
||||||
"session_id": sessionID,
|
"session_id": sessionID,
|
||||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||||
@@ -100,7 +102,7 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
setupAuth: func(r *http.Request) {
|
setupAuth: func(r *http.Request) {
|
||||||
sessionID := "expired-session"
|
sessionID := "expired-session"
|
||||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||||
"user_id": 123,
|
"user_id": uint(123),
|
||||||
"role_id": constants.Roles.Member,
|
"role_id": constants.Roles.Member,
|
||||||
"session_id": sessionID,
|
"session_id": sessionID,
|
||||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||||
@@ -116,7 +118,7 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
name: "Invalid Signature",
|
name: "Invalid Signature",
|
||||||
setupAuth: func(r *http.Request) {
|
setupAuth: func(r *http.Request) {
|
||||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||||
"user_id": 123,
|
"user_id": uint(123),
|
||||||
"session_id": "some-session",
|
"session_id": "some-session",
|
||||||
"exp": time.Now().Add(time.Hour).Unix(),
|
"exp": time.Now().Add(time.Hour).Unix(),
|
||||||
})
|
})
|
||||||
@@ -130,7 +132,7 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
name: "Invalid Signing Method",
|
name: "Invalid Signing Method",
|
||||||
setupAuth: func(r *http.Request) {
|
setupAuth: func(r *http.Request) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
|
||||||
"user_id": 123,
|
"user_id": uint(123),
|
||||||
"session_id": "some-session",
|
"session_id": "some-session",
|
||||||
"role_id": constants.Roles.Member,
|
"role_id": constants.Roles.Member,
|
||||||
"exp": time.Now().Add(time.Hour).Unix(),
|
"exp": time.Now().Add(time.Hour).Unix(),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CSPMiddleware() gin.HandlerFunc {
|
func CSPMiddleware() gin.HandlerFunc {
|
||||||
logger.Error.Printf("applying CSP")
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
policy := "default-src 'self'; " +
|
policy := "default-src 'self'; " +
|
||||||
"script-src 'self' 'unsafe-inline'" +
|
"script-src 'self' 'unsafe-inline'" +
|
||||||
@@ -35,7 +34,6 @@ func CSPMiddleware() gin.HandlerFunc {
|
|||||||
func CSPReportHandling(c *gin.Context) {
|
func CSPReportHandling(c *gin.Context) {
|
||||||
var report map[string]interface{}
|
var report map[string]interface{}
|
||||||
if err := c.BindJSON(&report); err != nil {
|
if err := c.BindJSON(&report); err != nil {
|
||||||
|
|
||||||
logger.Error.Printf("Couldn't Bind JSON: %#v", err)
|
logger.Error.Printf("Couldn't Bind JSON: %#v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/pkg/logger"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SecurityHeadersMiddleware() gin.HandlerFunc {
|
func SecurityHeadersMiddleware() gin.HandlerFunc {
|
||||||
logger.Error.Printf("applying headers")
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Header("X-Frame-Options", "DENY")
|
c.Header("X-Frame-Options", "DENY")
|
||||||
c.Header("X-Content-Type-Options", "nosniff")
|
c.Header("X-Content-Type-Options", "nosniff")
|
||||||
|
|||||||
54
go-backend/internal/models/Insurance.go
Normal file
54
go-backend/internal/models/Insurance.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Insurance struct {
|
||||||
|
ID uint `gorm:"primary_key" json:"id"`
|
||||||
|
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,15 +1,60 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"GoMembership/internal/config"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type BankAccount struct {
|
type BankAccount struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"`
|
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"-" binding:"-"`
|
||||||
|
UserID uint `gorm:"index" json:"user_id"`
|
||||||
|
MandateDateSigned time.Time `json:"mandate_date_signed"`
|
||||||
Bank string `json:"bank_name" binding:"safe_content"`
|
Bank string `json:"bank_name" binding:"safe_content"`
|
||||||
AccountHolderName string `json:"account_holder_name" binding:"safe_content"`
|
AccountHolderName string `json:"account_holder_name" binding:"safe_content"`
|
||||||
IBAN string `json:"iban"`
|
IBAN string `json:"iban" binding:"safe_content"`
|
||||||
BIC string `json:"bic"`
|
BIC string `json:"bic" binding:"safe_content"`
|
||||||
MandateReference string `gorm:"not null" json:"mandate_reference"`
|
MandateReference string `json:"mandate_reference" binding:"safe_content"`
|
||||||
ID uint `gorm:"primaryKey"`
|
}
|
||||||
|
|
||||||
|
func (b *BankAccount) Create(db *gorm.DB) error {
|
||||||
|
// b.ID = 0
|
||||||
|
// only the children the belongs to association gets a reference id
|
||||||
|
if err := db.Create(b).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info.Printf("BankAccount created: %#v", b)
|
||||||
|
return db.First(b, b.ID).Error // Refresh the object with all associations
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BankAccount) Update(db *gorm.DB) error {
|
||||||
|
var existingBankAccount BankAccount
|
||||||
|
|
||||||
|
logger.Info.Printf("updating BankAccount: %#v", b)
|
||||||
|
if err := db.First(&existingBankAccount, b.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Model(&existingBankAccount).Updates(b).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.First(b, b.ID).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:])
|
||||||
}
|
}
|
||||||
|
|||||||
165
go-backend/internal/models/car.go
Normal file
165
go-backend/internal/models/car.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Car struct {
|
||||||
|
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 `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 {
|
||||||
|
if err := tx.Preload(clause.Associations).Create(c).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info.Printf("car created: %#v", c)
|
||||||
|
return tx.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
First(c, c.ID).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Car) Update(db *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("Damages.Insurance").
|
||||||
|
Preload("Damages.Opponent").
|
||||||
|
First(&existingCar, c.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Car) Delete(db *gorm.DB) error {
|
||||||
|
return db.Select(clause.Associations).Delete(&c).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllCars(db *gorm.DB) ([]Car, error) {
|
||||||
|
var cars []Car
|
||||||
|
if err := db.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
Preload("Damages").
|
||||||
|
Preload("Insurances").
|
||||||
|
Find(&cars).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Car) FromID(db *gorm.DB, id uint) error {
|
||||||
|
var car Car
|
||||||
|
if err := db.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
Preload("Damages").
|
||||||
|
Preload("Insurances").
|
||||||
|
First(&car, id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = car
|
||||||
|
return nil
|
||||||
|
}
|
||||||
48
go-backend/internal/models/category.go
Normal file
48
go-backend/internal/models/category.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
|
Name string `json:"category" binding:"safe_content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info.Printf("Category created: %#v", c)
|
||||||
|
// Preload all associations to return the fully populated User
|
||||||
|
return tx.
|
||||||
|
First(c, c.ID).Error // Refresh the user object with all associations
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingCategory Category
|
||||||
|
|
||||||
|
logger.Info.Printf("updating Category: %#v", c)
|
||||||
|
if err := tx.First(&existingCategory, c.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingCategory).Updates(c).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(c, c.ID).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Delete(db *gorm.DB) error {
|
||||||
|
return db.Delete(&c).Error
|
||||||
|
}
|
||||||
@@ -1,17 +1,56 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Consent struct {
|
type Consent struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
FirstName string `gorm:"not null" json:"first_name" binding:"safe_content"`
|
FirstName string `gorm:"not null" json:"first_name" binding:"safe_content"`
|
||||||
LastName string `gorm:"not null" json:"last_name" binding:"safe_content"`
|
LastName string `gorm:"not null" json:"last_name" binding:"safe_content"`
|
||||||
Email string `json:"email" binding:"email,safe_content"`
|
Email string `json:"email" binding:"email,safe_content"`
|
||||||
ConsentType string `gorm:"not null" json:"consent_type" binding:"safe_content"`
|
ConsentType string `gorm:"not null" json:"consent_type" binding:"safe_content"`
|
||||||
ID uint `gorm:"primaryKey"`
|
UserID *uint `json:"user_id"`
|
||||||
User User
|
User *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"-" binding:"-"`
|
||||||
UserID uint
|
}
|
||||||
|
|
||||||
|
func (c *Consent) BeforeSave(tx *gorm.DB) (err error) {
|
||||||
|
c.Email = strings.ToLower(c.Email)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *Consent) Create(db *gorm.DB) error {
|
||||||
|
if err := db.Create(c).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info.Printf("Consent created: %#v", c)
|
||||||
|
return db.First(c, c.ID).Error // Refresh the user object with all associations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Consent) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingConsent Consent
|
||||||
|
|
||||||
|
logger.Info.Printf("updating Consent: %#v", c)
|
||||||
|
if err := tx.First(&existingConsent, c.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingConsent).Updates(c).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(c, c.ID).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Consent) Delete(db *gorm.DB) error {
|
||||||
|
return db.Delete(&c).Error
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Licence struct {
|
type Licence struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
|
UserID uint `json:"user_id"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
Status int8 `json:"status" binding:"omitempty,number"`
|
Status int8 `json:"status" binding:"omitempty,number"`
|
||||||
@@ -13,10 +18,49 @@ type Licence struct {
|
|||||||
IssuedDate time.Time `json:"issued_date" binding:"omitempty"`
|
IssuedDate time.Time `json:"issued_date" binding:"omitempty"`
|
||||||
ExpirationDate time.Time `json:"expiration_date" binding:"omitempty"`
|
ExpirationDate time.Time `json:"expiration_date" binding:"omitempty"`
|
||||||
IssuingCountry string `json:"country" binding:"safe_content"`
|
IssuingCountry string `json:"country" binding:"safe_content"`
|
||||||
Categories []Category `json:"categories" gorm:"many2many:licence_2_categories"`
|
Categories []*Category `json:"categories" gorm:"many2many:licence_2_categories"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Category struct {
|
func (l *Licence) BeforeSafe(tx *gorm.DB) error {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
if err := tx.Model(l).Association("Categories").Replace(l.Categories); err != nil {
|
||||||
Name string `json:"category" binding:"safe_content"`
|
return fmt.Errorf("failed to link categories: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Licence) Create(db *gorm.DB) error {
|
||||||
|
if err := db.Omit("Categories").Create(l).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Model(&l).Association("Categories").Replace(l.Categories); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info.Printf("Licence created: %#v", l)
|
||||||
|
return db.Preload("Categories").First(l, l.ID).Error // Refresh the object with Categories
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Licence) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingLicence Licence
|
||||||
|
|
||||||
|
logger.Info.Printf("updating Licence: %#v", l)
|
||||||
|
if err := tx.First(&existingLicence, l.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingLicence).Updates(l).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(l, l.ID).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Licence) Delete(db *gorm.DB) error {
|
||||||
|
return db.Delete(&l).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
|
||||||
|
}
|
||||||
@@ -1,15 +1,59 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type Membership struct {
|
type Membership struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
UserID uint `gorm:"index" json:"user_id"`
|
||||||
|
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"-" binding:"-"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
StartDate time.Time `json:"start_date"`
|
StartDate time.Time `json:"start_date"`
|
||||||
EndDate time.Time `json:"end_date"`
|
EndDate time.Time `json:"end_date"`
|
||||||
Status int8 `json:"status" binding:"number,safe_content"`
|
Status int8 `json:"status" binding:"number,safe_content"`
|
||||||
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"`
|
Subscription Subscription `gorm:"foreignKey:SubscriptionID" json:"subscription"`
|
||||||
SubscriptionModelID uint `json:"subsription_model_id"`
|
SubscriptionID uint `json:"subscription_id"`
|
||||||
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
|
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
|
||||||
ID uint `json:"id"`
|
}
|
||||||
|
|
||||||
|
func (m *Membership) BeforeSave(tx *gorm.DB) error {
|
||||||
|
m.SubscriptionID = m.Subscription.ID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Membership) Create(db *gorm.DB) error {
|
||||||
|
if err := db.Create(m).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info.Printf("Membership created: %#v", m)
|
||||||
|
|
||||||
|
return db.Preload("Subscription").First(m, m.ID).Error // Refresh the user object with Subscription
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Membership) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingMembership Membership
|
||||||
|
|
||||||
|
logger.Info.Printf("updating Membership: %#v", m)
|
||||||
|
if err := tx.First(&existingMembership, m.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingMembership).Updates(m).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(m, m.ID).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Membership) Delete(db *gorm.DB) error {
|
||||||
|
return db.Delete(&m).Error
|
||||||
}
|
}
|
||||||
|
|||||||
58
go-backend/internal/models/subscription.go
Normal file
58
go-backend/internal/models/subscription.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Subscription struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Name string `gorm:"uniqueIndex:idx_subscriptions_name" json:"name" binding:"required,safe_content"`
|
||||||
|
Details string `json:"details" binding:"safe_content"`
|
||||||
|
Conditions string `json:"conditions" binding:"safe_content"`
|
||||||
|
RequiredMembershipField string `json:"required_membership_field" binding:"safe_content"`
|
||||||
|
MonthlyFee float32 `json:"monthly_fee"`
|
||||||
|
HourlyRate float32 `json:"hourly_rate"`
|
||||||
|
IncludedPerYear int16 `json:"included_hours_per_year"`
|
||||||
|
IncludedPerMonth int16 `json:"included_hours_per_month"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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("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 *Subscription) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingSubscription Subscription
|
||||||
|
|
||||||
|
logger.Info.Printf("updating Subscription: %#v", s)
|
||||||
|
if err := tx.First(&existingSubscription, s.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingSubscription).Updates(s).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(s, s.ID).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Subscription) Delete(db *gorm.DB) error {
|
||||||
|
return db.Delete(&s).Error
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SubscriptionModel struct {
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Name string `gorm:"unique" json:"name" binding:"required"`
|
|
||||||
Details string `json:"details"`
|
|
||||||
Conditions string `json:"conditions"`
|
|
||||||
RequiredMembershipField string `json:"required_membership_field"`
|
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
|
||||||
MonthlyFee float32 `json:"monthly_fee"`
|
|
||||||
HourlyRate float32 `json:"hourly_rate"`
|
|
||||||
IncludedPerYear int16 `json:"included_hours_per_year"`
|
|
||||||
IncludedPerMonth int16 `json:"included_hours_per_month"`
|
|
||||||
}
|
|
||||||
@@ -1,67 +1,175 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"GoMembership/internal/config"
|
||||||
|
"GoMembership/internal/constants"
|
||||||
|
"GoMembership/pkg/errors"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt *time.Time `gorm:"index"`
|
DeletedAt *time.Time
|
||||||
DateOfBirth time.Time `gorm:"not null" json:"dateofbirth" binding:"required_unless=RoleID 0,safe_content"`
|
DateOfBirth time.Time `gorm:"not null" json:"dateofbirth" binding:"required_unless=RoleID 0,safe_content"`
|
||||||
Company string `json:"company" binding:"omitempty,omitnil,safe_content"`
|
Company string `json:"company" binding:"omitempty,omitnil,safe_content"`
|
||||||
Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"`
|
Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"`
|
||||||
Notes string `json:"notes" binding:"safe_content"`
|
Notes string `json:"notes" binding:"safe_content"`
|
||||||
FirstName string `gorm:"not null" json:"first_name" binding:"required,safe_content"`
|
FirstName string `gorm:"not null" json:"first_name" binding:"required,safe_content"`
|
||||||
Password string `json:"password" binding:"safe_content"`
|
Password string `json:"password" binding:"safe_content"`
|
||||||
Email string `gorm:"unique;not null" json:"email" binding:"required,email,safe_content"`
|
Email string `gorm:"uniqueIndex:idx_users_email,not null" json:"email" binding:"required,email,safe_content"`
|
||||||
LastName string `gorm:"not null" json:"last_name" binding:"required,safe_content"`
|
LastName string `gorm:"not null" json:"last_name" binding:"required,safe_content"`
|
||||||
ProfilePicture string `json:"profile_picture" binding:"omitempty,omitnil,image,safe_content"`
|
|
||||||
Address string `gorm:"not null" json:"address" binding:"required,safe_content"`
|
Address string `gorm:"not null" json:"address" binding:"required,safe_content"`
|
||||||
ZipCode string `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"`
|
ZipCode string `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"`
|
||||||
City string `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"`
|
City string `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"`
|
||||||
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
|
Consents []Consent `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
|
||||||
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
BankAccount *BankAccount `gorm:"foreignkey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"bank_account"`
|
||||||
BankAccountID uint
|
Verifications []Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||||
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
Membership *Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"membership"`
|
||||||
VerificationID uint
|
Licence *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"licence"`
|
||||||
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
|
|
||||||
MembershipID uint
|
|
||||||
Licence *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"licence"`
|
|
||||||
LicenceID uint
|
|
||||||
PaymentStatus int8 `json:"payment_status"`
|
|
||||||
Status int8 `json:"status"`
|
Status int8 `json:"status"`
|
||||||
RoleID int8 `json:"role_id"`
|
RoleID int8 `json:"role_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
|
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
|
||||||
if u.BankAccount.ID != 0 && u.BankAccount.MandateReference == "" {
|
if u.BankAccount != nil && u.BankAccount.MandateReference == "" {
|
||||||
mandateReference := u.GenerateMandateReference()
|
u.BankAccount.MandateReference = u.BankAccount.GenerateMandateReference(u.ID)
|
||||||
|
u.BankAccount.Update(tx)
|
||||||
return tx.Model(&u.BankAccount).Update("MandateReference", mandateReference).Error
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GenerateMandateReference() string {
|
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
|
||||||
return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN)
|
u.Email = strings.ToLower(u.Email)
|
||||||
}
|
if u.Password != "" {
|
||||||
|
hash, err := argon2id.CreateHash(u.Password, argon2id.DefaultParams)
|
||||||
func (u *User) SetPassword(plaintextPassword string) error {
|
|
||||||
if plaintextPassword == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.Password = hash
|
u.Password = hash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Create(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Preload(clause.Associations).Create(u).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
err := db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingUser User
|
||||||
|
|
||||||
|
logger.Info.Printf("updating user: %#v", u)
|
||||||
|
if err := tx.
|
||||||
|
First(&existingUser, u.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Update the user's main fields
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return errors.ErrNoRowsAffected
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Verifications != nil {
|
||||||
|
if err := tx.Save(u.Verifications).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Licence != nil {
|
||||||
|
if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
Preload("Membership.Subscription").
|
||||||
|
Preload("Licence.Categories").
|
||||||
|
First(&u, u.ID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Delete(db *gorm.DB) error {
|
||||||
|
return db.Unscoped().Delete(&User{}, "id = ?", u.ID).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.Subscription").
|
||||||
|
Preload("Licence.Categories").
|
||||||
|
First(&user, userID)
|
||||||
|
if result.Error != nil {
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
return gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
*u = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) FromEmail(db *gorm.DB, email *string) error {
|
||||||
|
var user User
|
||||||
|
result := db.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
Preload("Membership.Subscription").
|
||||||
|
Preload("Licence.Categories").
|
||||||
|
Where("email = ?", email).First(&user)
|
||||||
|
if result.Error != nil {
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
return gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
*u = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) FromContext(db *gorm.DB, c *gin.Context) error {
|
||||||
|
tokenString, err := c.Cookie("jwt")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jwtUserID, err := extractUserIDFrom(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = u.FromID(db, &jwtUserID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +177,121 @@ func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
|
|||||||
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
|
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) PasswordExists() bool {
|
||||||
|
return u.Password != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsVerified() bool {
|
||||||
|
return u.Status > constants.DisabledStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) HasPrivilege(privilege int8) bool {
|
||||||
|
return u.RoleID >= privilege
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsAdmin() bool {
|
||||||
|
return u.RoleID == constants.Roles.Admin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsMember() bool {
|
||||||
|
return u.RoleID == constants.Roles.Member
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsSupporter() bool {
|
||||||
|
return u.RoleID == constants.Roles.Supporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) SetVerification(verificationType string) (*Verification, error) {
|
||||||
|
if u.Verifications == nil {
|
||||||
|
u.Verifications = []Verification{}
|
||||||
|
}
|
||||||
|
v, err := CreateVerification(verificationType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
u.Verifications = append(u.Verifications, *v)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) FindVerification(verificationType string) (*Verification, error) {
|
||||||
|
if u.Verifications == nil {
|
||||||
|
return nil, errors.ErrNoData
|
||||||
|
}
|
||||||
|
vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool { return vsl.Type == verificationType })
|
||||||
|
if vi == -1 {
|
||||||
|
return nil, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
return &u.Verifications[vi], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 errors.ErrNoData
|
||||||
|
}
|
||||||
|
|
||||||
|
vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool {
|
||||||
|
return vsl.Type == verificationType && vsl.VerificationToken == token
|
||||||
|
})
|
||||||
|
|
||||||
|
if vi == -1 {
|
||||||
|
logger.Error.Printf("Couldn't find verification in users verifications")
|
||||||
|
return errors.ErrNotFound
|
||||||
|
}
|
||||||
|
return u.Verifications[vi].Validate()
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Safe() map[string]interface{} {
|
func (u *User) Safe() map[string]interface{} {
|
||||||
|
var membership map[string]interface{} = nil
|
||||||
|
var licence map[string]interface{} = nil
|
||||||
|
var bankAccount map[string]interface{} = nil
|
||||||
|
if u.Membership != nil {
|
||||||
|
membership = map[string]interface{}{
|
||||||
|
"id": u.Membership.ID,
|
||||||
|
"start_date": u.Membership.StartDate,
|
||||||
|
"end_date": u.Membership.EndDate,
|
||||||
|
"status": u.Membership.Status,
|
||||||
|
"subscription": map[string]interface{}{
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Licence != nil {
|
||||||
|
licence = map[string]interface{}{
|
||||||
|
"id": u.Licence.ID,
|
||||||
|
"number": u.Licence.Number,
|
||||||
|
"status": u.Licence.Status,
|
||||||
|
"issued_date": u.Licence.IssuedDate,
|
||||||
|
"expiration_date": u.Licence.ExpirationDate,
|
||||||
|
"country": u.Licence.IssuingCountry,
|
||||||
|
"categories": u.Licence.Categories,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.BankAccount != nil {
|
||||||
|
bankAccount = map[string]interface{}{
|
||||||
|
"id": u.BankAccount.ID,
|
||||||
|
"mandate_date_signed": u.BankAccount.MandateDateSigned,
|
||||||
|
"bank": u.BankAccount.Bank,
|
||||||
|
"account_holder_name": u.BankAccount.AccountHolderName,
|
||||||
|
"iban": u.BankAccount.IBAN,
|
||||||
|
"bic": u.BankAccount.BIC,
|
||||||
|
"mandate_reference": u.BankAccount.MandateReference,
|
||||||
|
}
|
||||||
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"email": u.Email,
|
"email": u.Email,
|
||||||
"first_name": u.FirstName,
|
"first_name": u.FirstName,
|
||||||
@@ -84,47 +306,66 @@ func (u *User) Safe() map[string]interface{} {
|
|||||||
"role_id": u.RoleID,
|
"role_id": u.RoleID,
|
||||||
"company": u.Company,
|
"company": u.Company,
|
||||||
"dateofbirth": u.DateOfBirth,
|
"dateofbirth": u.DateOfBirth,
|
||||||
"membership": map[string]interface{}{
|
"membership": membership,
|
||||||
"id": u.Membership.ID,
|
"licence": licence,
|
||||||
"start_date": u.Membership.StartDate,
|
"bank_account": bankAccount,
|
||||||
"end_date": u.Membership.EndDate,
|
|
||||||
"status": u.Membership.Status,
|
|
||||||
"subscription_model": 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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"licence": map[string]interface{}{
|
|
||||||
"id": 0,
|
|
||||||
},
|
|
||||||
"bank_account": map[string]interface{}{
|
|
||||||
"id": u.BankAccount.ID,
|
|
||||||
"mandate_date_signed": u.BankAccount.MandateDateSigned,
|
|
||||||
"bank": u.BankAccount.Bank,
|
|
||||||
"account_holder_name": u.BankAccount.AccountHolderName,
|
|
||||||
"iban": u.BankAccount.IBAN,
|
|
||||||
"bic": u.BankAccount.BIC,
|
|
||||||
"mandate_reference": u.BankAccount.MandateReference,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Licence != nil {
|
|
||||||
result["licence"] = map[string]interface{}{
|
|
||||||
"id": u.Licence.ID,
|
|
||||||
"number": u.Licence.Number,
|
|
||||||
"status": u.Licence.Status,
|
|
||||||
"issued_date": u.Licence.IssuedDate,
|
|
||||||
"expiration_date": u.Licence.ExpirationDate,
|
|
||||||
"country": u.Licence.IssuingCountry,
|
|
||||||
"categories": u.Licence.Categories,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractUserIDFrom(tokenString string) (uint, error) {
|
||||||
|
jwtSigningMethod := jwt.SigningMethodHS256
|
||||||
|
jwtParser := jwt.NewParser(jwt.WithValidMethods([]string{jwtSigningMethod.Alg()}))
|
||||||
|
token, err := jwtParser.Parse(tokenString, func(_ *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(config.Auth.JWTSecret), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle parsing errors (excluding expiration error)
|
||||||
|
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) || token == nil {
|
||||||
|
logger.Error.Printf("Error parsing token: %v", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
logger.Error.Print("Invalid token claims structure")
|
||||||
|
return 0, fmt.Errorf("invalid token claims format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required session_id claim
|
||||||
|
if _, exists := claims["session_id"]; !exists {
|
||||||
|
logger.Error.Print("Missing session_id in token claims")
|
||||||
|
return 0, fmt.Errorf("missing session_id claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return token, claims, and original error (might be expiration)
|
||||||
|
|
||||||
|
if _, exists := claims["session_id"]; !exists {
|
||||||
|
logger.Error.Print("Missing session_id in token claims")
|
||||||
|
return 0, fmt.Errorf("missing session_id claim")
|
||||||
|
}
|
||||||
|
id, ok := claims["user_id"]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("missing user_id claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(id.(float64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
|
||||||
|
logger.Error.Printf("where: %#v", where)
|
||||||
|
var users []User
|
||||||
|
result := db.
|
||||||
|
Preload(clause.Associations).
|
||||||
|
Preload("Membership.Subscription").
|
||||||
|
Preload("Licence.Categories").
|
||||||
|
Where(where).Find(&users)
|
||||||
|
if result.Error != nil {
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return &users, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,78 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"GoMembership/internal/utils"
|
||||||
|
"GoMembership/pkg/errors"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type Verification struct {
|
type Verification struct {
|
||||||
UpdatedAt time.Time
|
gorm.Model
|
||||||
CreatedAt time.Time
|
VerifiedAt *time.Time `json:"verified_at"`
|
||||||
VerifiedAt *time.Time `gorm:"Default:NULL" json:"verified_at"`
|
|
||||||
VerificationToken string `json:"token"`
|
VerificationToken string `json:"token"`
|
||||||
ID uint `gorm:"primaryKey"`
|
UserID uint `json:"user_id"`
|
||||||
UserID uint `gorm:"unique;" json:"user_id"`
|
|
||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Verification) Create(db *gorm.DB) error {
|
||||||
|
if err := db.Create(v).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info.Printf("verification created: %#v", v)
|
||||||
|
// Preload all associations to return the fully populated object
|
||||||
|
return db.First(v, v.ID).Error // Refresh the verification object with all associations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Verification) Update(db *gorm.DB) error {
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Check if the user exists in the database
|
||||||
|
var existingVerification Verification
|
||||||
|
|
||||||
|
logger.Info.Printf("updating verification: %#v", v)
|
||||||
|
if err := tx.First(&existingVerification, v.ID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&existingVerification).Updates(v).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.First(v, v.ID).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)
|
||||||
|
}
|
||||||
|
|||||||
69
go-backend/internal/repositories/car_repository.go
Normal file
69
go-backend/internal/repositories/car_repository.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/database"
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/pkg/errors"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CarRepository interface defines the CRUD operations
|
||||||
|
type CarRepositoryInterface interface {
|
||||||
|
Create(car *models.Car) (*models.Car, error)
|
||||||
|
GetByID(id uint) (*models.Car, error)
|
||||||
|
GetAll() ([]models.Car, error)
|
||||||
|
Update(car *models.Car) (*models.Car, error)
|
||||||
|
Delete(id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CarRepository struct{}
|
||||||
|
|
||||||
|
// Create a new car
|
||||||
|
func (r *CarRepository) Create(car *models.Car) (*models.Car, error) {
|
||||||
|
if err := database.DB.Create(car).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID fetches a car by its ID
|
||||||
|
func (r *CarRepository) GetByID(id uint) (*models.Car, error) {
|
||||||
|
var car models.Car
|
||||||
|
if err := database.DB.Where("id = ?", id).First(&car).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errors.ErrNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll retrieves all cars
|
||||||
|
func (r *CarRepository) GetAll() ([]models.Car, error) {
|
||||||
|
var cars []models.Car
|
||||||
|
if err := database.DB.Find(&cars).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update an existing car
|
||||||
|
func (r *CarRepository) Update(car *models.Car) (*models.Car, error) {
|
||||||
|
if err := database.DB.Save(car).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a car (soft delete)
|
||||||
|
func (r *CarRepository) Delete(id uint) error {
|
||||||
|
result := database.DB.Delete(&models.Car{}, id)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return errors.ErrNotFound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -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.membership_id = memberships.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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoMembership/internal/database"
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *UserRepository) SetUserStatus(id uint, status uint) error {
|
|
||||||
return database.DB.Model(&models.User{}).Where("id = ?", id).Update("status", status).Error
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
|
|
||||||
"GoMembership/internal/database"
|
|
||||||
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
"GoMembership/pkg/errors"
|
|
||||||
"GoMembership/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserRepositoryInterface interface {
|
|
||||||
CreateUser(user *models.User) (uint, error)
|
|
||||||
UpdateUser(user *models.User) (*models.User, error)
|
|
||||||
GetUsers(where map[string]interface{}) (*[]models.User, error)
|
|
||||||
GetUserByEmail(email string) (*models.User, error)
|
|
||||||
IsVerified(userID *uint) (bool, error)
|
|
||||||
GetVerificationOfToken(token *string, verificationType *string) (*models.Verification, error)
|
|
||||||
SetVerificationToken(verification *models.Verification) (token string, err error)
|
|
||||||
DeleteVerification(id uint, verificationType string) error
|
|
||||||
DeleteUser(id uint) error
|
|
||||||
SetUserStatus(id uint, status uint) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRepository struct{}
|
|
||||||
|
|
||||||
func (ur *UserRepository) DeleteUser(id uint) error {
|
|
||||||
return database.DB.Delete(&models.User{}, "id = ?", id).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func PasswordExists(userID *uint) (bool, error) {
|
|
||||||
var user models.User
|
|
||||||
result := database.DB.Select("password").First(&user, userID)
|
|
||||||
if result.Error != nil {
|
|
||||||
return false, result.Error
|
|
||||||
}
|
|
||||||
return user.Password != "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) CreateUser(user *models.User) (uint, error) {
|
|
||||||
result := database.DB.Create(user)
|
|
||||||
if result.Error != nil {
|
|
||||||
logger.Error.Printf("Create User error: %#v", result.Error)
|
|
||||||
return 0, result.Error
|
|
||||||
}
|
|
||||||
return user.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
|
|
||||||
if user == nil {
|
|
||||||
return nil, errors.ErrNoData
|
|
||||||
}
|
|
||||||
|
|
||||||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
// Check if the user exists in the database
|
|
||||||
var existingUser models.User
|
|
||||||
|
|
||||||
if err := tx.Preload(clause.Associations).
|
|
||||||
Preload("Membership").
|
|
||||||
Preload("Membership.SubscriptionModel").
|
|
||||||
Preload("Licence").
|
|
||||||
Preload("Licence.Categories").
|
|
||||||
First(&existingUser, user.ID).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Update the user's main fields
|
|
||||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password").Updates(user)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return errors.ErrNoRowsAffected
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Password != "" {
|
|
||||||
if err := tx.Model(&models.User{}).
|
|
||||||
Where("id = ?", user.ID).
|
|
||||||
Update("Password", user.Password).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the Membership if provided
|
|
||||||
if user.Membership.ID != 0 {
|
|
||||||
if err := tx.Model(&existingUser.Membership).Updates(user.Membership).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace categories if Licence and Categories are provided
|
|
||||||
if user.Licence != nil {
|
|
||||||
if err := tx.Model(&user.Licence).Association("Categories").Replace(user.Licence.Categories); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedUser models.User
|
|
||||||
if err := database.DB.Preload("Licence.Categories").
|
|
||||||
Preload("Membership").
|
|
||||||
First(&updatedUser, user.ID).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &updatedUser, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) GetUsers(where map[string]interface{}) (*[]models.User, error) {
|
|
||||||
var users []models.User
|
|
||||||
result := database.DB.
|
|
||||||
Preload(clause.Associations).
|
|
||||||
Preload("Membership.SubscriptionModel").
|
|
||||||
Preload("Licence.Categories").
|
|
||||||
Where(where).Find(&users)
|
|
||||||
if result.Error != nil {
|
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
return &users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserByID(userID *uint) (*models.User, error) {
|
|
||||||
var user models.User
|
|
||||||
result := database.DB.
|
|
||||||
Preload(clause.Associations).
|
|
||||||
Preload("Membership").
|
|
||||||
Preload("Membership.SubscriptionModel").
|
|
||||||
Preload("Licence.Categories").
|
|
||||||
First(&user, userID)
|
|
||||||
if result.Error != nil {
|
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) GetUserByEmail(email string) (*models.User, error) {
|
|
||||||
var user models.User
|
|
||||||
result := database.DB.Where("email = ?", email).First(&user)
|
|
||||||
if result.Error != nil {
|
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoMembership/internal/constants"
|
|
||||||
"GoMembership/internal/database"
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ur *UserRepository) IsVerified(userID *uint) (bool, error) {
|
|
||||||
var user models.User
|
|
||||||
result := database.DB.Select("status").First(&user, userID)
|
|
||||||
if result.Error != nil {
|
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
|
||||||
return false, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return false, result.Error
|
|
||||||
}
|
|
||||||
return user.Status > constants.DisabledStatus, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) GetVerificationOfToken(token *string, verificationType *string) (*models.Verification, error) {
|
|
||||||
|
|
||||||
var emailVerification models.Verification
|
|
||||||
result := database.DB.Where("verification_token = ? AND type = ?", token, verificationType).First(&emailVerification)
|
|
||||||
if result.Error != nil {
|
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
|
||||||
return nil, gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
return &emailVerification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) SetVerificationToken(verification *models.Verification) (token string, err error) {
|
|
||||||
|
|
||||||
result := database.DB.Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "user_id"}},
|
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"verification_token", "created_at", "type"}),
|
|
||||||
}).Create(&verification)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return "", result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return verification.VerificationToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UserRepository) DeleteVerification(id uint, verificationType string) error {
|
|
||||||
result := database.DB.Where("user_id = ? AND type = ?", id, verificationType).Delete(&models.Verification{})
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController, licenceController *controllers.LicenceController) {
|
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController, licenceController *controllers.LicenceController, carController *controllers.CarController) {
|
||||||
router.GET("/api/users/verify", userController.VerifyMailHandler)
|
router.GET("/api/users/verify/:id", userController.VerifyMailHandler)
|
||||||
router.POST("/api/users/register", userController.RegisterUser)
|
router.POST("/api/users/register", userController.RegisterUser)
|
||||||
router.POST("/api/users/contact", contactController.RelayContactRequest)
|
router.POST("/api/users/contact", contactController.RelayContactRequest)
|
||||||
router.POST("/api/users/password/request-change", userController.RequestPasswordChangeHandler)
|
router.POST("/api/users/password/request-change", userController.RequestPasswordChangeHandler)
|
||||||
@@ -19,12 +19,17 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
|||||||
userRouter := router.Group("/api/auth")
|
userRouter := router.Group("/api/auth")
|
||||||
userRouter.Use(middlewares.AuthMiddleware())
|
userRouter.Use(middlewares.AuthMiddleware())
|
||||||
{
|
{
|
||||||
|
userRouter.GET("/cars", carController.GetAll)
|
||||||
|
userRouter.PUT("/cars", carController.Update)
|
||||||
|
userRouter.POST("/cars", carController.Create)
|
||||||
|
userRouter.DELETE("/cars", carController.Delete)
|
||||||
userRouter.GET("/users/current", userController.CurrentUserHandler)
|
userRouter.GET("/users/current", userController.CurrentUserHandler)
|
||||||
userRouter.POST("/logout", userController.LogoutHandler)
|
userRouter.POST("/logout", userController.LogoutHandler)
|
||||||
userRouter.PUT("/users", userController.UpdateHandler)
|
userRouter.PUT("/users", userController.UpdateHandler)
|
||||||
userRouter.POST("/users", userController.RegisterUser)
|
userRouter.POST("/users", userController.RegisterUser)
|
||||||
userRouter.GET("/users", userController.GetAllUsers)
|
userRouter.GET("/users", userController.GetAllUsers)
|
||||||
userRouter.DELETE("/users", userController.DeleteUser)
|
userRouter.DELETE("/users", userController.DeleteUser)
|
||||||
|
userRouter.PATCH("/users/activate", userController.CreatePasswordHandler)
|
||||||
userRouter.GET("/subscriptions", membershipcontroller.GetSubscriptions)
|
userRouter.GET("/subscriptions", membershipcontroller.GetSubscriptions)
|
||||||
userRouter.PUT("/subscriptions", membershipcontroller.UpdateHandler)
|
userRouter.PUT("/subscriptions", membershipcontroller.UpdateHandler)
|
||||||
userRouter.POST("/subscriptions", membershipcontroller.RegisterSubscription)
|
userRouter.POST("/subscriptions", membershipcontroller.RegisterSubscription)
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ import (
|
|||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shutdownChannel = make(chan struct{})
|
var shutdownChannel = make(chan struct{})
|
||||||
var srv *http.Server
|
var srv *http.Server
|
||||||
|
|
||||||
// Run initializes the server configuration, sets up services and controllers, and starts the HTTP server.
|
// Run initializes the server configuration, sets up services and controllers, and starts the HTTP server.
|
||||||
func Run() {
|
func Run(db *gorm.DB) {
|
||||||
|
|
||||||
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
|
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
|
||||||
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
|
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
|
||||||
@@ -36,19 +37,19 @@ func Run() {
|
|||||||
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
|
||||||
|
|
||||||
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
|
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}
|
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
|
||||||
|
|
||||||
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
|
||||||
licenceService := &services.LicenceService{Repo: licenceRepo}
|
licenceService := &services.LicenceService{Repo: licenceRepo}
|
||||||
|
userService := &services.UserService{DB: db, Licences: licenceRepo}
|
||||||
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
|
||||||
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
|
||||||
|
|
||||||
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, LicenceService: licenceService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, LicenceService: licenceService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
||||||
membershipController := &controllers.MembershipController{Service: *membershipService, UserController: userController}
|
membershipController := &controllers.MembershipController{Service: membershipService, UserService: userService}
|
||||||
licenceController := &controllers.LicenceController{Service: *licenceService}
|
licenceController := &controllers.LicenceController{Service: licenceService}
|
||||||
contactController := &controllers.ContactController{EmailService: emailService}
|
contactController := &controllers.ContactController{EmailService: emailService}
|
||||||
|
carService := &services.CarService{DB: db}
|
||||||
|
carController := &controllers.CarController{S: carService, UserService: userService}
|
||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
// gin.SetMode(gin.ReleaseMode)
|
// gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -64,8 +65,8 @@ func Run() {
|
|||||||
limiter := middlewares.NewIPRateLimiter(config.Security.Ratelimits.Limit, config.Security.Ratelimits.Burst)
|
limiter := middlewares.NewIPRateLimiter(config.Security.Ratelimits.Limit, config.Security.Ratelimits.Burst)
|
||||||
router.Use(middlewares.RateLimitMiddleware(limiter))
|
router.Use(middlewares.RateLimitMiddleware(limiter))
|
||||||
|
|
||||||
routes.RegisterRoutes(router, userController, membershipController, contactController, licenceController)
|
routes.RegisterRoutes(router, userController, membershipController, contactController, licenceController, carController)
|
||||||
validation.SetupValidators()
|
validation.SetupValidators(db)
|
||||||
|
|
||||||
logger.Info.Println("Starting server on :8080")
|
logger.Info.Println("Starting server on :8080")
|
||||||
srv = &http.Server{
|
srv = &http.Server{
|
||||||
|
|||||||
67
go-backend/internal/services/car_service.go
Normal file
67
go-backend/internal/services/car_service.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CarServiceInterface interface {
|
||||||
|
Create(car *models.Car) (*models.Car, error)
|
||||||
|
Update(car *models.Car) (*models.Car, error)
|
||||||
|
Delete(carID *uint) error
|
||||||
|
FromID(id uint) (*models.Car, error)
|
||||||
|
GetAll() (*[]models.Car, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CarService struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new car
|
||||||
|
func (s *CarService) Create(car *models.Car) (*models.Car, error) {
|
||||||
|
|
||||||
|
err := car.Create(s.DB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update an existing car
|
||||||
|
func (s *CarService) Update(car *models.Car) (*models.Car, error) {
|
||||||
|
err := car.Update(s.DB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a car (soft delete)
|
||||||
|
func (s *CarService) Delete(carID *uint) error {
|
||||||
|
var car models.Car
|
||||||
|
err := car.FromID(s.DB, *carID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return car.Delete(s.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID fetches a car by its ID
|
||||||
|
func (s *CarService) FromID(id uint) (*models.Car, error) {
|
||||||
|
car := &models.Car{}
|
||||||
|
err := car.FromID(s.DB, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return car, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll retrieves all cars
|
||||||
|
func (s *CarService) GetAll() (*[]models.Car, error) {
|
||||||
|
cars, err := models.GetAllCars(s.DB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cars, nil
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ func (s *EmailService) SendEmail(to string, subject string, body string, bodyTXT
|
|||||||
|
|
||||||
func ParseTemplate(filename string, data interface{}) (string, error) {
|
func ParseTemplate(filename string, data interface{}) (string, error) {
|
||||||
// Read the email template file
|
// Read the email template file
|
||||||
|
logger.Error.Printf("Data: %#v", data)
|
||||||
templateDir := config.Templates.MailPath
|
templateDir := config.Templates.MailPath
|
||||||
tpl, err := template.ParseFiles(templateDir + "/" + filename)
|
tpl, err := template.ParseFiles(templateDir + "/" + filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,13 +71,18 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
|
|||||||
LastName string
|
LastName string
|
||||||
Token string
|
Token string
|
||||||
BASEURL string
|
BASEURL string
|
||||||
|
UserID uint
|
||||||
|
Logo string
|
||||||
}{
|
}{
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Token: *token,
|
Token: *token,
|
||||||
BASEURL: config.Site.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
|
UserID: user.ID,
|
||||||
|
Logo: config.Templates.LogoURI,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Error.Printf("USERIID: %#v", user.ID)
|
||||||
subject := constants.MailVerificationSubject
|
subject := constants.MailVerificationSubject
|
||||||
body, err := ParseTemplate("mail_verification.tmpl", data)
|
body, err := ParseTemplate("mail_verification.tmpl", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,6 +92,39 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
|
|||||||
return s.SendEmail(user.Email, subject, body, "", "")
|
return s.SendEmail(user.Email, subject, body, "", "")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func (s *EmailService) SendGrantBackendAccessEmail(user *models.User, token *string) error {
|
||||||
|
// Prepare data to be injected into the template
|
||||||
|
data := struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Token string
|
||||||
|
BASEURL string
|
||||||
|
FRONTEND_PATH string
|
||||||
|
UserID uint
|
||||||
|
Logo string
|
||||||
|
}{
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Token: *token,
|
||||||
|
FRONTEND_PATH: config.Site.FrontendPath,
|
||||||
|
BASEURL: config.Site.BaseURL,
|
||||||
|
UserID: user.ID,
|
||||||
|
Logo: config.Templates.LogoURI,
|
||||||
|
}
|
||||||
|
|
||||||
|
subject := constants.MailGrantBackendAccessSubject
|
||||||
|
htmlBody, err := ParseTemplate("mail_grant_backend_access.tmpl", data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Print("Couldn't send grant backend access mail")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plainBody, err := ParseTemplate("mail_grant_backend_access.txt.tmpl", data)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Print("Couldn't parse password mail")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.SendEmail(user.Email, subject, htmlBody, plainBody, "")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error {
|
func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error {
|
||||||
// Prepare data to be injected into the template
|
// Prepare data to be injected into the template
|
||||||
@@ -97,6 +135,7 @@ func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string)
|
|||||||
BASEURL string
|
BASEURL string
|
||||||
FRONTEND_PATH string
|
FRONTEND_PATH string
|
||||||
UserID uint
|
UserID uint
|
||||||
|
Logo string
|
||||||
}{
|
}{
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
@@ -104,6 +143,7 @@ func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string)
|
|||||||
FRONTEND_PATH: config.Site.FrontendPath,
|
FRONTEND_PATH: config.Site.FrontendPath,
|
||||||
BASEURL: config.Site.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
Logo: config.Templates.LogoURI,
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := constants.MailChangePasswordSubject
|
subject := constants.MailChangePasswordSubject
|
||||||
@@ -136,10 +176,10 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
|
|||||||
}{
|
}{
|
||||||
Company: user.Company,
|
Company: user.Company,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
MembershipModel: user.Membership.SubscriptionModel.Name,
|
MembershipModel: user.Membership.Subscription.Name,
|
||||||
MembershipID: user.Membership.ID,
|
MembershipID: user.Membership.ID,
|
||||||
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
|
||||||
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
RentalFee: float32(user.Membership.Subscription.HourlyRate),
|
||||||
BASEURL: config.Site.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
WebsiteTitle: config.Site.WebsiteTitle,
|
WebsiteTitle: config.Site.WebsiteTitle,
|
||||||
Logo: config.Templates.LogoURI,
|
Logo: config.Templates.LogoURI,
|
||||||
@@ -183,10 +223,10 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
|
|||||||
Company: user.Company,
|
Company: user.Company,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
MembershipModel: user.Membership.SubscriptionModel.Name,
|
MembershipModel: user.Membership.Subscription.Name,
|
||||||
MembershipID: user.Membership.ID,
|
MembershipID: user.Membership.ID,
|
||||||
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
MembershipFee: float32(user.Membership.Subscription.MonthlyFee),
|
||||||
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
RentalFee: float32(user.Membership.Subscription.HourlyRate),
|
||||||
Address: user.Address,
|
Address: user.Address,
|
||||||
ZipCode: user.ZipCode,
|
ZipCode: user.ZipCode,
|
||||||
City: user.City,
|
City: user.City,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LicenceInterface interface {
|
type LicenceServiceInterface interface {
|
||||||
GetAllCategories() ([]models.Category, error)
|
GetAllCategories() ([]models.Category, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import (
|
|||||||
type MembershipServiceInterface interface {
|
type MembershipServiceInterface interface {
|
||||||
RegisterMembership(membership *models.Membership) (uint, error)
|
RegisterMembership(membership *models.Membership) (uint, error)
|
||||||
FindMembershipByUserID(userID uint) (*models.Membership, error)
|
FindMembershipByUserID(userID uint) (*models.Membership, error)
|
||||||
RegisterSubscription(subscription *models.SubscriptionModel) (uint, error)
|
RegisterSubscription(subscription *models.Subscription) (uint, error)
|
||||||
UpdateSubscription(subscription *models.SubscriptionModel) (*models.SubscriptionModel, error)
|
UpdateSubscription(subscription *models.Subscription) (*models.Subscription, error)
|
||||||
DeleteSubscription(id *uint, name *string) error
|
DeleteSubscription(id *uint, name *string) error
|
||||||
GetSubscriptionModelNames() ([]string, error)
|
GetSubscriptionNames() ([]string, error)
|
||||||
GetSubscriptionByName(modelname *string) (*models.SubscriptionModel, error)
|
GetSubscriptionByName(modelname *string) (*models.Subscription, error)
|
||||||
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
|
GetSubscriptions(where map[string]interface{}) (*[]models.Subscription, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MembershipService struct {
|
type MembershipService struct {
|
||||||
Repo repositories.MembershipRepositoryInterface
|
Repo repositories.MembershipRepositoryInterface
|
||||||
SubscriptionRepo repositories.SubscriptionModelsRepositoryInterface
|
SubscriptionRepo repositories.SubscriptionsRepositoryInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *MembershipService) RegisterMembership(membership *models.Membership) (uint, error) {
|
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)
|
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)
|
existingSubscription, err := repositories.GetSubscriptionByName(&subscription.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,19 +82,19 @@ func (service *MembershipService) FindMembershipByUserID(userID uint) (*models.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Membership_Subscriptions
|
// Membership_Subscriptions
|
||||||
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (uint, error) {
|
func (service *MembershipService) RegisterSubscription(subscription *models.Subscription) (uint, error) {
|
||||||
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
|
return service.SubscriptionRepo.CreateSubscription(subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *MembershipService) GetSubscriptionModelNames() ([]string, error) {
|
func (service *MembershipService) GetSubscriptionNames() ([]string, error) {
|
||||||
return service.SubscriptionRepo.GetSubscriptionModelNames()
|
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)
|
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 {
|
if where == nil {
|
||||||
where = map[string]interface{}{}
|
where = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoMembership/internal/constants"
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *UserService) HandlePasswordChangeRequest(user *models.User) (token string, err error) {
|
|
||||||
// Deactivate user and reset Verification
|
|
||||||
if err := s.SetUserStatus(user.ID, constants.DisabledStatus); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.RevokeVerification(&user.ID, constants.VerificationTypes.Password); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a token
|
|
||||||
return s.SetVerificationToken(&user.ID, &constants.VerificationTypes.Password)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
func (s *UserService) SetUserStatus(id uint, status uint) error {
|
|
||||||
return s.Repo.SetUserStatus(id, status)
|
|
||||||
}
|
|
||||||
@@ -8,65 +8,81 @@ import (
|
|||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserServiceInterface interface {
|
type UserServiceInterface interface {
|
||||||
RegisterUser(user *models.User) (id uint, token string, err error)
|
Register(user *models.User) (id uint, token string, err error)
|
||||||
GetUserByEmail(email string) (*models.User, error)
|
Update(user *models.User) (*models.User, error)
|
||||||
GetUserByID(id uint) (*models.User, error)
|
Delete(id *uint) error
|
||||||
|
FromContext(c *gin.Context) (*models.User, error)
|
||||||
|
FromID(id *uint) (*models.User, error)
|
||||||
|
FromEmail(email *string) (*models.User, error)
|
||||||
GetUsers(where map[string]interface{}) (*[]models.User, error)
|
GetUsers(where map[string]interface{}) (*[]models.User, error)
|
||||||
UpdateUser(user *models.User) (*models.User, error)
|
|
||||||
DeleteUser(lastname string, id uint) error
|
|
||||||
SetUserStatus(id uint, status uint) error
|
|
||||||
VerifyUser(token *string, verificationType *string) (*models.Verification, error)
|
|
||||||
SetVerificationToken(id *uint, verificationType *string) (string, error)
|
|
||||||
RevokeVerification(id *uint, verificationType string) error
|
|
||||||
HandlePasswordChangeRequest(user *models.User) (token string, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
Repo repositories.UserRepositoryInterface
|
|
||||||
Licences repositories.LicenceInterface
|
Licences repositories.LicenceInterface
|
||||||
|
DB *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) DeleteUser(lastname string, id uint) error {
|
func (s *UserService) FromContext(c *gin.Context) (*models.User, error) {
|
||||||
if id == 0 || lastname == "" {
|
var user models.User
|
||||||
return errors.ErrNoData
|
if err := user.FromContext(s.DB, c); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
user, err := service.GetUserByID(id)
|
func (s *UserService) FromID(id *uint) (*models.User, error) {
|
||||||
if err != nil {
|
var user models.User
|
||||||
|
if err := user.FromID(s.DB, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) FromEmail(email *string) (*models.User, error) {
|
||||||
|
var user models.User
|
||||||
|
if err := user.FromEmail(s.DB, email); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) Delete(id *uint) error {
|
||||||
|
var user models.User
|
||||||
|
if err := user.FromID(s.DB, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if user == nil {
|
|
||||||
return errors.ErrUserNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return service.Repo.DeleteUser(id)
|
return user.Delete(s.DB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) UpdateUser(user *models.User) (*models.User, error) {
|
func (s *UserService) Update(user *models.User) (*models.User, error) {
|
||||||
|
|
||||||
if user.ID == 0 {
|
var existingUser models.User
|
||||||
return nil, errors.ErrUserNotFound
|
if err := existingUser.FromID(s.DB, &user.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
user.Membership.ID = existingUser.Membership.ID
|
||||||
user.SetPassword(user.Password)
|
if existingUser.Licence != nil {
|
||||||
|
user.Licence.ID = existingUser.Licence.ID
|
||||||
|
}
|
||||||
|
user.BankAccount.ID = existingUser.BankAccount.ID
|
||||||
|
|
||||||
// Validate subscription model
|
// Validate subscription model
|
||||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
|
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.ErrSubscriptionNotFound
|
return nil, errors.ErrSubscriptionNotFound
|
||||||
}
|
}
|
||||||
user.Membership.SubscriptionModel = *selectedModel
|
user.Membership.Subscription = *selectedModel
|
||||||
user.Membership.SubscriptionModelID = selectedModel.ID
|
user.Membership.SubscriptionID = selectedModel.ID
|
||||||
|
|
||||||
updatedUser, err := service.Repo.UpdateUser(user)
|
if err := user.Update(s.DB); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
return nil, errors.ErrUserNotFound
|
return nil, errors.ErrUserNotFound
|
||||||
}
|
}
|
||||||
@@ -75,42 +91,36 @@ func (service *UserService) UpdateUser(user *models.User) (*models.User, error)
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return user, nil
|
||||||
return updatedUser, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) RegisterUser(user *models.User) (id uint, token string, err error) {
|
func (s *UserService) Register(user *models.User) (id uint, token string, err error) {
|
||||||
|
|
||||||
user.SetPassword(user.Password)
|
|
||||||
|
|
||||||
|
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", errors.ErrSubscriptionNotFound
|
||||||
|
}
|
||||||
|
user.Membership.Subscription = *selectedModel
|
||||||
|
user.Membership.SubscriptionID = selectedModel.ID
|
||||||
user.Status = constants.UnverifiedStatus
|
user.Status = constants.UnverifiedStatus
|
||||||
user.CreatedAt = time.Now()
|
|
||||||
user.UpdatedAt = time.Now()
|
|
||||||
user.PaymentStatus = constants.AwaitingPaymentStatus
|
|
||||||
user.BankAccount.MandateDateSigned = time.Now()
|
user.BankAccount.MandateDateSigned = time.Now()
|
||||||
id, err = service.Repo.CreateUser(user)
|
v, err := user.SetVerification(constants.VerificationTypes.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err = service.SetVerificationToken(&id, &constants.VerificationTypes.Email)
|
if err := user.Create(s.DB); err != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
return id, token, nil
|
|
||||||
|
return user.ID, v.VerificationToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) GetUserByID(id uint) (*models.User, error) {
|
// GetUsers returns a list of users based on the provided where clause.
|
||||||
return repositories.GetUserByID(&id)
|
// if where == nil: all users are returned
|
||||||
}
|
func (s *UserService) GetUsers(where map[string]interface{}) (*[]models.User, error) {
|
||||||
|
|
||||||
func (service *UserService) GetUserByEmail(email string) (*models.User, error) {
|
|
||||||
return service.Repo.GetUserByEmail(email)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *UserService) GetUsers(where map[string]interface{}) (*[]models.User, error) {
|
|
||||||
if where == nil {
|
if where == nil {
|
||||||
where = map[string]interface{}{}
|
where = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
return service.Repo.GetUsers(where)
|
return models.GetUsersWhere(s.DB, where)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
"GoMembership/internal/utils"
|
|
||||||
"GoMembership/pkg/errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *UserService) SetVerificationToken(id *uint, verificationType *string) (string, error) {
|
|
||||||
|
|
||||||
token, err := utils.GenerateVerificationToken()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user is already verified
|
|
||||||
verified, err := s.Repo.IsVerified(id)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if verified {
|
|
||||||
return "", errors.ErrAlreadyVerified
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the Verification record
|
|
||||||
verification := models.Verification{
|
|
||||||
UserID: *id,
|
|
||||||
VerificationToken: token,
|
|
||||||
Type: *verificationType,
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Repo.SetVerificationToken(&verification)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UserService) RevokeVerification(id *uint, verificationType string) error {
|
|
||||||
return s.Repo.DeleteVerification(*id, verificationType)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *UserService) VerifyUser(token *string, verificationType *string) (*models.Verification, error) {
|
|
||||||
verification, err := service.Repo.GetVerificationOfToken(token, verificationType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user is already verified
|
|
||||||
verified, err := service.Repo.IsVerified(&verification.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if verified {
|
|
||||||
return nil, errors.ErrAlreadyVerified
|
|
||||||
}
|
|
||||||
t := time.Now()
|
|
||||||
verification.VerifiedAt = &t
|
|
||||||
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
@@ -30,10 +30,6 @@ func GenerateRandomString(length int) (string, error) {
|
|||||||
return base64.URLEncoding.EncodeToString(bytes), nil
|
return base64.URLEncoding.EncodeToString(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateVerificationToken() (string, error) {
|
|
||||||
return GenerateRandomString(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeMail(message string) (*Email, error) {
|
func DecodeMail(message string) (*Email, error) {
|
||||||
msg, err := mail.ReadMessage(strings.NewReader(message))
|
msg, err := mail.ReadMessage(strings.NewReader(message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func HandleUserUpdateError(c *gin.Context, err error) {
|
|||||||
case errors.ErrDuplicateEntry:
|
case errors.ErrDuplicateEntry:
|
||||||
RespondWithError(c, err, "User Unique constraint failed", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.Duplicate)
|
RespondWithError(c, err, "User Unique constraint failed", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.Duplicate)
|
||||||
case errors.ErrSubscriptionNotFound:
|
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:
|
default:
|
||||||
RespondWithError(c, err, "Couldn't update user", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
RespondWithError(c, err, "Couldn't update user", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
||||||
}
|
}
|
||||||
@@ -53,46 +53,34 @@ func HandleUserUpdateError(c *gin.Context, err error) {
|
|||||||
func HandleSubscriptionDeleteError(c *gin.Context, err error) {
|
func HandleSubscriptionDeleteError(c *gin.Context, err error) {
|
||||||
switch err {
|
switch err {
|
||||||
case errors.ErrNoData:
|
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:
|
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:
|
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:
|
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:
|
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) {
|
func HandleSubscriptionUpdateError(c *gin.Context, err error) {
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
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 {
|
} else {
|
||||||
switch err {
|
switch err {
|
||||||
case errors.ErrSubscriptionNotFound:
|
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:
|
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:
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleVerifyUserError(c *gin.Context, err error) {
|
|
||||||
if err.Error() == "record not found" {
|
|
||||||
RespondWithError(c, err, "Couldn't find verification. This is most probably a outdated token.", http.StatusGone, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
|
||||||
}
|
|
||||||
switch err {
|
|
||||||
case errors.ErrAlreadyVerified:
|
|
||||||
RespondWithError(c, err, "User already changed password", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
|
|
||||||
default:
|
|
||||||
RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDeleteUserError(c *gin.Context, err error) {
|
func HandleDeleteUserError(c *gin.Context, err error) {
|
||||||
if err.Error() == "record not found" {
|
if err.Error() == "record not found" {
|
||||||
RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
package utils
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/internal/models"
|
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HasPrivilige(user *models.User, privilige int8) bool {
|
|
||||||
return user.RoleID >= privilige
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterAllowedStructFields filters allowed fields recursively in a struct and modifies structToModify in place.
|
// FilterAllowedStructFields filters allowed fields recursively in a struct and modifies structToModify in place.
|
||||||
func FilterAllowedStructFields(input interface{}, existing interface{}, allowedFields map[string]bool, prefix string) error {
|
func FilterAllowedStructFields(input interface{}, existing interface{}, allowedFields map[string]bool, prefix string) error {
|
||||||
v := reflect.ValueOf(input)
|
v := reflect.ValueOf(input)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package utils
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -10,7 +10,7 @@ type User struct {
|
|||||||
Age int
|
Age int
|
||||||
Address *Address
|
Address *Address
|
||||||
Tags []string
|
Tags []string
|
||||||
License License
|
Licence Licence
|
||||||
}
|
}
|
||||||
|
|
||||||
type Address struct {
|
type Address struct {
|
||||||
@@ -18,7 +18,7 @@ type Address struct {
|
|||||||
Country string
|
Country string
|
||||||
}
|
}
|
||||||
|
|
||||||
type License struct {
|
type Licence struct {
|
||||||
ID string
|
ID string
|
||||||
Categories []string
|
Categories []string
|
||||||
}
|
}
|
||||||
@@ -98,22 +98,22 @@ func TestFilterAllowedStructFields(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Filter slice of structs",
|
name: "Filter slice of structs",
|
||||||
input: &User{
|
input: &User{
|
||||||
License: License{
|
Licence: Licence{
|
||||||
ID: "123",
|
ID: "123",
|
||||||
Categories: []string{"A", "B"},
|
Categories: []string{"A", "B"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
existing: &User{
|
existing: &User{
|
||||||
License: License{
|
Licence: Licence{
|
||||||
ID: "456",
|
ID: "456",
|
||||||
Categories: []string{"C"},
|
Categories: []string{"C"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
allowedFields: map[string]bool{
|
allowedFields: map[string]bool{
|
||||||
"License.ID": true,
|
"Licence.ID": true,
|
||||||
},
|
},
|
||||||
expectedResult: &User{
|
expectedResult: &User{
|
||||||
License: License{
|
Licence: Licence{
|
||||||
ID: "123", // Allowed field
|
ID: "123", // Allowed field
|
||||||
Categories: []string{"C"}, // Kept from existing
|
Categories: []string{"C"}, // Kept from existing
|
||||||
},
|
},
|
||||||
@@ -2,39 +2,37 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/internal/repositories"
|
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateMembership(sl validator.StructLevel) {
|
func validateMembership(db *gorm.DB, user *models.User, sl validator.StructLevel) {
|
||||||
membership := sl.Current().Interface().(models.User).Membership
|
if user.Membership.Subscription.RequiredMembershipField != "" {
|
||||||
if membership.SubscriptionModel.RequiredMembershipField != "" {
|
switch user.Membership.Subscription.RequiredMembershipField {
|
||||||
switch membership.SubscriptionModel.RequiredMembershipField {
|
|
||||||
case "ParentMembershipID":
|
case "ParentMembershipID":
|
||||||
if err := CheckParentMembershipID(membership); err != nil {
|
if err := CheckParentMembershipID(db, user); err != nil {
|
||||||
logger.Error.Printf("Error ParentMembershipValidation: %v", err.Error())
|
logger.Error.Printf("Error ParentMembershipValidation: %v", err.Error())
|
||||||
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
|
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
|
||||||
"RequiredMembershipField", "invalid", "")
|
"RequiredMembershipField", "invalid", "")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logger.Error.Printf("Error no matching RequiredMembershipField: %v", errors.ErrInvalidValue.Error())
|
logger.Error.Printf("Error no matching RequiredMembershipField: %v", errors.ErrInvalidValue.Error())
|
||||||
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
|
sl.ReportError(user.Membership.ParentMembershipID, user.Membership.Subscription.RequiredMembershipField,
|
||||||
"RequiredMembershipField", "not_implemented", "")
|
"RequiredMembershipField", "not_implemented", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckParentMembershipID(membership models.Membership) error {
|
func CheckParentMembershipID(db *gorm.DB, user *models.User) error {
|
||||||
|
|
||||||
if membership.ParentMembershipID == 0 {
|
if user.Membership.ParentMembershipID == 0 {
|
||||||
return errors.ValErrParentIDNotSet
|
return errors.ValErrParentIDNotSet
|
||||||
} else {
|
} else {
|
||||||
_, err := repositories.GetUserByID(&membership.ParentMembershipID)
|
var parent models.User
|
||||||
if err != nil {
|
if err := parent.FromID(db, &user.Membership.ParentMembershipID); err != nil {
|
||||||
return errors.ValErrParentIDNotFound
|
return errors.ValErrParentIDNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/internal/models"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetupValidators() {
|
func SetupValidators(db *gorm.DB) {
|
||||||
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
// Register custom validators
|
// Register custom validators
|
||||||
v.RegisterValidation("safe_content", ValidateSafeContent)
|
v.RegisterValidation("safe_content", ValidateSafeContent)
|
||||||
|
|
||||||
// Register struct-level validations
|
// Register struct-level validations
|
||||||
v.RegisterStructValidation(ValidateUser, models.User{})
|
// v.RegisterStructValidation(ValidateUserFactory(db), models.User{})
|
||||||
v.RegisterStructValidation(ValidateSubscription, models.SubscriptionModel{})
|
// v.RegisterStructValidation(ValidateSubscription, models.Subscription{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ import (
|
|||||||
|
|
||||||
// ValidateNewSubscription validates a new subscription model being created
|
// ValidateNewSubscription validates a new subscription model being created
|
||||||
func ValidateSubscription(sl validator.StructLevel) {
|
func ValidateSubscription(sl validator.StructLevel) {
|
||||||
subscription := sl.Current().Interface().(models.SubscriptionModel)
|
subscription := sl.Current().Interface().(models.Subscription)
|
||||||
|
|
||||||
if subscription.Name == "" {
|
if subscription.Name == "" {
|
||||||
sl.ReportError(subscription.Name, "Name", "name", "required", "")
|
sl.ReportError(subscription.Name, "Name", "name", "required", "")
|
||||||
}
|
}
|
||||||
|
if sl.Parent().Type().Name() == "" {
|
||||||
if sl.Parent().Type().Name() == "MembershipData" {
|
|
||||||
// This is modifying a subscription directly
|
// This is modifying a subscription directly
|
||||||
if subscription.Details == "" {
|
if subscription.Details == "" {
|
||||||
sl.ReportError(subscription.Details, "Details", "details", "required", "")
|
sl.ReportError(subscription.Details, "Details", "details", "required", "")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"GoMembership/internal/constants"
|
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
@@ -10,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
passwordvalidator "github.com/wagslane/go-password-validator"
|
passwordvalidator "github.com/wagslane/go-password-validator"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var passwordErrorTranslations = map[string]string{
|
var passwordErrorTranslations = map[string]string{
|
||||||
@@ -21,24 +21,28 @@ var passwordErrorTranslations = map[string]string{
|
|||||||
"using numbers": "server.validation.numbers",
|
"using numbers": "server.validation.numbers",
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateUser(sl validator.StructLevel) {
|
func ValidateUserFactory(db *gorm.DB) validator.StructLevelFunc {
|
||||||
|
return func(sl validator.StructLevel) {
|
||||||
|
validateUser(db, sl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUser(db *gorm.DB, sl validator.StructLevel) {
|
||||||
user := sl.Current().Interface().(models.User)
|
user := sl.Current().Interface().(models.User)
|
||||||
|
|
||||||
isSuper := user.RoleID >= constants.Roles.Admin
|
// validate subscription
|
||||||
isSupporter := user.RoleID == constants.Roles.Supporter
|
if user.Membership.Subscription.Name == "" {
|
||||||
// validate subscriptionModel
|
sl.ReportError(user.Membership.Subscription.Name, "subscription.name", "name", "required", "")
|
||||||
if user.Membership.SubscriptionModel.Name == "" {
|
|
||||||
sl.ReportError(user.Membership.SubscriptionModel.Name, "subscription.name", "name", "required", "")
|
|
||||||
} else {
|
} else {
|
||||||
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.SubscriptionModel.Name)
|
selectedModel, err := repositories.GetSubscriptionByName(&user.Membership.Subscription.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error finding subscription model for user %v: %v", user.Email, err)
|
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 {
|
} else {
|
||||||
user.Membership.SubscriptionModel = *selectedModel
|
user.Membership.Subscription = *selectedModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isSupporter {
|
if user.IsSupporter() {
|
||||||
if user.BankAccount.IBAN != "" {
|
if user.BankAccount.IBAN != "" {
|
||||||
validateBankAccount(sl)
|
validateBankAccount(sl)
|
||||||
}
|
}
|
||||||
@@ -54,9 +58,9 @@ func ValidateUser(sl validator.StructLevel) {
|
|||||||
if user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) {
|
if user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) {
|
||||||
sl.ReportError(user.DateOfBirth, "user.user", "user.dateofbirth", "age", "")
|
sl.ReportError(user.DateOfBirth, "user.user", "user.dateofbirth", "age", "")
|
||||||
}
|
}
|
||||||
validateMembership(sl)
|
validateMembership(db, &user, sl)
|
||||||
|
|
||||||
if isSuper {
|
if user.IsAdmin() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,34 +2,6 @@ package errors
|
|||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
type ValidationKeys struct {
|
|
||||||
Invalid string
|
|
||||||
InternalServerError string
|
|
||||||
InvalidJson string
|
|
||||||
Unauthorized string
|
|
||||||
InvalidSubscriptionModel string
|
|
||||||
UserNotFoundWrongPassword string
|
|
||||||
JwtGenerationFailed string
|
|
||||||
Duplicate string
|
|
||||||
InvalidUserID string
|
|
||||||
PasswordAlreadyChanged string
|
|
||||||
UserDisabled string
|
|
||||||
NoAuthToken string
|
|
||||||
NotFound string
|
|
||||||
InUse string
|
|
||||||
UndeliveredVerificationMail string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValidationFields struct {
|
|
||||||
General string
|
|
||||||
ParentMemberShipID string
|
|
||||||
SubscriptionModel string
|
|
||||||
Login string
|
|
||||||
Email string
|
|
||||||
User string
|
|
||||||
Licences string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
ErrUserNotFound = errors.New("user not found")
|
ErrUserNotFound = errors.New("user not found")
|
||||||
@@ -54,6 +26,37 @@ var (
|
|||||||
ErrInvalidSubscriptionData = errors.New("Provided subscription data is invalid. Immutable fields where changed.")
|
ErrInvalidSubscriptionData = errors.New("Provided subscription data is invalid. Immutable fields where changed.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ValidationKeys struct {
|
||||||
|
Invalid string
|
||||||
|
InternalServerError string
|
||||||
|
InvalidJSON string
|
||||||
|
InvalidUserID string
|
||||||
|
InvalidSubscription string
|
||||||
|
Unauthorized string
|
||||||
|
UserNotFoundWrongPassword string
|
||||||
|
JwtGenerationFailed string
|
||||||
|
Duplicate string
|
||||||
|
UserDisabled string
|
||||||
|
PasswordAlreadyChanged string
|
||||||
|
NoAuthToken string
|
||||||
|
NotFound string
|
||||||
|
InUse string
|
||||||
|
UndeliveredVerificationMail string
|
||||||
|
UserAlreadyVerified string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidationFields struct {
|
||||||
|
General string
|
||||||
|
ParentMembershipID string
|
||||||
|
Subscription string
|
||||||
|
Login string
|
||||||
|
Email string
|
||||||
|
User string
|
||||||
|
Licences string
|
||||||
|
Verification string
|
||||||
|
Car string
|
||||||
|
}
|
||||||
|
|
||||||
var Responses = struct {
|
var Responses = struct {
|
||||||
Keys ValidationKeys
|
Keys ValidationKeys
|
||||||
Fields ValidationFields
|
Fields ValidationFields
|
||||||
@@ -61,7 +64,9 @@ var Responses = struct {
|
|||||||
Keys: ValidationKeys{
|
Keys: ValidationKeys{
|
||||||
Invalid: "server.validation.invalid",
|
Invalid: "server.validation.invalid",
|
||||||
InternalServerError: "server.error.internal_server_error",
|
InternalServerError: "server.error.internal_server_error",
|
||||||
InvalidJson: "server.error.invalid_json",
|
InvalidJSON: "server.error.invalid_json",
|
||||||
|
InvalidUserID: "server.validation.invalid_user_id",
|
||||||
|
InvalidSubscription: "server.validation.invalid_subscription",
|
||||||
Unauthorized: "server.error.unauthorized",
|
Unauthorized: "server.error.unauthorized",
|
||||||
UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password",
|
UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password",
|
||||||
JwtGenerationFailed: "server.error.jwt_generation_failed",
|
JwtGenerationFailed: "server.error.jwt_generation_failed",
|
||||||
@@ -72,14 +77,21 @@ var Responses = struct {
|
|||||||
NotFound: "server.error.not_found",
|
NotFound: "server.error.not_found",
|
||||||
InUse: "server.error.in_use",
|
InUse: "server.error.in_use",
|
||||||
UndeliveredVerificationMail: "server.error.undelivered_verification_mail",
|
UndeliveredVerificationMail: "server.error.undelivered_verification_mail",
|
||||||
|
UserAlreadyVerified: "server.validation.user_already_verified",
|
||||||
},
|
},
|
||||||
Fields: ValidationFields{
|
Fields: ValidationFields{
|
||||||
General: "server.general",
|
General: "server.general",
|
||||||
ParentMemberShipID: "parent_membership_id",
|
ParentMembershipID: "parent_membership_id",
|
||||||
SubscriptionModel: "subscription_model",
|
Subscription: "subscription",
|
||||||
Login: "user.login",
|
Login: "user.login",
|
||||||
Email: "user.email",
|
Email: "user.email",
|
||||||
User: "user.user",
|
User: "user.user",
|
||||||
Licences: "licence",
|
Licences: "licence",
|
||||||
|
Verification: "verification",
|
||||||
|
Car: "car",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Is(err error, target error) bool {
|
||||||
|
return errors.Is(err, target)
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
><img
|
><img
|
||||||
alt="Dörpsmobil Hasloh"
|
alt="Dörpsmobil Hasloh"
|
||||||
src="{{.BASEURL}}/images/CarsharingSH-Hasloh-LOGO.jpeg"
|
src="{{.Logo}}"
|
||||||
style="
|
style="
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
><img
|
><img
|
||||||
alt="{{.WebsiteTitle}}"
|
alt="{{.WebsiteTitle}}"
|
||||||
src="{{.BASEURL}}{{.Logo}}"
|
src="{{.Logo}}"
|
||||||
style="
|
style="
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
177
go-backend/templates/email/mail_grant_backend_access.tmpl
Normal file
177
go-backend/templates/email/mail_grant_backend_access.tmpl
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
background-color: #f2f5f7;
|
||||||
|
color: #242424;
|
||||||
|
font-family: Optima, Candara, "Noto Sans", source-sans-pro,
|
||||||
|
sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.15008px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 32px 0;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
align="center"
|
||||||
|
width="100%"
|
||||||
|
style="margin: 0 auto; max-width: 600px; background-color: #ffffff"
|
||||||
|
role="presentation"
|
||||||
|
cellspacing="0"
|
||||||
|
cellpadding="0"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr style="width: 100%">
|
||||||
|
<td>
|
||||||
|
<div style="padding: 24px 24px 24px 24px; text-align: center">
|
||||||
|
<a
|
||||||
|
href="{{.BASEURL}}"
|
||||||
|
style="text-decoration: none"
|
||||||
|
target="_blank"
|
||||||
|
><img
|
||||||
|
alt="Dörpsmobil Hasloh"
|
||||||
|
src="{{.Logo}}"
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
"
|
||||||
|
/></a>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
|
||||||
|
Moin {{.FirstName}} {{.LastName}} 👋,
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
|
||||||
|
hiermit erhältst Du Zugnag zu Deinem Dörpsmobil Hasloh Account.
|
||||||
|
</div>
|
||||||
|
<div style="padding: 16px 0px 16px 0px">
|
||||||
|
<hr
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #cccccc;
|
||||||
|
margin: 0;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 16px 24px; text-align: center; font-family: Arial, sans-serif; color: #333;">
|
||||||
|
<p style="margin-bottom: 16px;">
|
||||||
|
Mit diesem Link kannst Du Dich dann in Deinen <strong>Dörpsmobil Hasloh</strong> Account anmelden:
|
||||||
|
</p>
|
||||||
|
<p style="margin: 16px 0; font-size: 18px;">
|
||||||
|
<a href="{{.BASEURL}}{{.FRONTEND_PATH}}/"
|
||||||
|
style="display: inline-block; padding: 12px 20px; background-color: #007bff; color: white; text-decoration: none; font-weight: bold; border-radius: 5px;">
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p style="margin-top: 16px;">
|
||||||
|
Dafür musst Du aber zunächst noch Dein Passwort erstellen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center; padding: 16px 24px 16px 24px">
|
||||||
|
<a
|
||||||
|
href=" {{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}"
|
||||||
|
style="
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #3e9bfc;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
padding: 16px 32px;
|
||||||
|
text-decoration: none;
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="
|
||||||
|
letter-spacing: 32px;
|
||||||
|
mso-font-width: -100%;
|
||||||
|
mso-text-raise: 48;
|
||||||
|
"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
><span>
|
||||||
|
Passwort erstellen
|
||||||
|
</span
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="letter-spacing: 32px; mso-font-width: -100%"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 24px 0px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Alternativ kannst Du auch diesen Link in Deinem Browser öffnen:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px 24px 16px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
||||||
|
Mit Freundlichen Grüßen,
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
padding: 16px 24px 16px 24px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Der Vorstand
|
||||||
|
</div>
|
||||||
|
<div style="padding: 16px 24px 16px 24px">
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src="{{.BASEURL}}/images/favicon_hu5543b2b337a87a169e2c722ef0122802_211442_96x0_resize_lanczos_3.png"
|
||||||
|
height="80"
|
||||||
|
width="80"
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
object-fit: cover;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 80px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
Moin {{.FirstName}} {{.LastName}} 👋,
|
||||||
|
|
||||||
|
hiermit erhältst Du Zugnag zu Deinem Dörpsmobil Hasloh Account.
|
||||||
|
|
||||||
|
Mit diesem Link kannst Du Dich dann in Deinen Dörpsmobil Hasloh Account anmelden:
|
||||||
|
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/
|
||||||
|
|
||||||
|
einloggen.
|
||||||
|
|
||||||
|
Dafür musst Du aber zunächst noch Dein Passwort erstellen:
|
||||||
|
|
||||||
|
{{.BASEURL}}{{.FRONTEND_PATH}}/auth/password/change/{{.UserID}}?token={{.Token}}
|
||||||
|
|
||||||
|
Mit Freundlichen Grüßen,
|
||||||
|
|
||||||
|
Der Vorstand
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
><img
|
><img
|
||||||
alt="{{.WebsiteTitle}}"
|
alt="{{.WebsiteTitle}}"
|
||||||
src="{{.BASEURL}}{{.Logo}}"
|
src="{{.Logo}}"
|
||||||
style="
|
style="
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
><img
|
><img
|
||||||
alt="Dörpsmobil Hasloh"
|
alt="Dörpsmobil Hasloh"
|
||||||
src="{{.BASEURL}}/images/CarsharingSH-Hasloh-LOGO.jpeg"
|
src="{{.Logo}}"
|
||||||
style="
|
style="
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="text-align: center; padding: 16px 24px 16px 24px">
|
<div style="text-align: center; padding: 16px 24px 16px 24px">
|
||||||
<a
|
<a
|
||||||
href="{{.BASEURL}}/api/users/verify?token={{.Token}}"
|
href="{{.BASEURL}}/api/users/verify/{{.UserID}}?token={{.Token}}"
|
||||||
style="
|
style="
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ noch Ihre Emailadresse indem Sie hier klicken:
|
|||||||
|
|
||||||
E-Mail Adresse bestätigen
|
E-Mail Adresse bestätigen
|
||||||
|
|
||||||
{{.BASEURL}}/api/users/verify?token={{.Token}}
|
{{.BASEURL}}/api/users/verify/{{.UserID}}?token={{.Token}}
|
||||||
|
|
||||||
Nachdem wir Ihre E-Mail Adresse bestätigen konnten, schicken wir
|
Nachdem wir Ihre E-Mail Adresse bestätigen konnten, schicken wir
|
||||||
Ihnen alle weiteren Informationen zu. Wir freuen uns auf die
|
Ihnen alle weiteren Informationen zu. Wir freuen uns auf die
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
><img
|
><img
|
||||||
alt="{{.WebsiteTitle}}"
|
alt="{{.WebsiteTitle}}"
|
||||||
src="{{.BASEURL}}{{.Logo}}"
|
src="{{.Logo}}"
|
||||||
style="
|
style="
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -80,22 +80,61 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Mitgliedsbeitrag</strong>: Solange wir noch kein
|
<strong>Führerscheinverifikation</strong>:
|
||||||
Fahrzeug im Betrieb haben, zahlst Du sinnvollerweise auch
|
Dein Führerschein wird bei der Anmeldung bei unserem Dienstleister Moqo verifiziert.
|
||||||
keinen Mitgliedsbeitrag. Es ist zur Zeit der 1.1.2025 als
|
|
||||||
Startdatum geplant.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Führerscheinverifikation</strong>: Weitere
|
Bitte melde Dich nun noch auf der Webseite von Moqo für Deinen gewählten
|
||||||
Informationen zur Verifikation deines Führerscheins folgen
|
Tarif an:
|
||||||
in Kürze. Du musst nichts weiter tun, wir werden uns bei dir
|
|
||||||
melden, sobald es notwendig ist.
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<strong>Moqo App</strong>: Wir werden die Moqo App nutzen,
|
</li>
|
||||||
um das Fahrzeug ausleihen zu können. Wenn Du schon mal einen
|
<strong>Moqo Anmeldung(Bitte UNBEDINGT AUSFÜLLEN, sonst ist kein Ausleihen möglich)</strong>:
|
||||||
ersten Eindruck von dem Buchungsvorgang haben möchtest,
|
<div style="text-align: center; padding: 0px 24px 16px 24px">
|
||||||
schaue Dir gerne dieses kurze Video an:
|
<a
|
||||||
|
href="https://portal.moqo.de/js_sign_up/18013114#/subscription-selection"
|
||||||
|
style="
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f45050;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="
|
||||||
|
letter-spacing: 20px;
|
||||||
|
mso-font-width: -100%;
|
||||||
|
mso-text-raise: 30;
|
||||||
|
"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
><span>MOQO Anmeldung</span
|
||||||
|
><span
|
||||||
|
><!--[if mso
|
||||||
|
]><i
|
||||||
|
style="letter-spacing: 20px; mso-font-width: -100%"
|
||||||
|
hidden
|
||||||
|
> </i
|
||||||
|
><!
|
||||||
|
[endif]--></span
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<li>
|
||||||
|
<strong>Moqo App</strong>:
|
||||||
|
Wir nutzen die Moqo App, damit Du das Fahrzeug mit Deinem Handy
|
||||||
|
entsperren und Deine Buchungen verwalten kannst.
|
||||||
|
Wenn Du schon mal einen ersten Eindruck von dem Buchungsvorgang
|
||||||
|
haben möchtest, schaue Dir gerne dieses kurze Video an:
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,8 +311,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
||||||
Wir danken dir herzlich für dein Vertrauen in uns und freuen uns
|
Wir danken Dir herzlich für Dein Vertrauen und freuen uns drauf, Dich bald
|
||||||
darauf, dich hoffentlich bald mit einem Auto begrüßen zu dürfen.
|
Dörpsmobil fahrend in Hasloh zu sehen!
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
|
||||||
<p>Mit freundlichen Grüßen,</p>
|
<p>Mit freundlichen Grüßen,</p>
|
||||||
|
|||||||
@@ -12,23 +12,24 @@ Hier einige wichtige Informationen für dich:
|
|||||||
Preis/Monat: {{.MembershipFee}}
|
Preis/Monat: {{.MembershipFee}}
|
||||||
Preis/h: {{.RentalFee}}
|
Preis/h: {{.RentalFee}}
|
||||||
|
|
||||||
Mitgliedsbeitrag: Solange wir noch kein
|
Führerscheinverifikation: Dein Führerschein wird bei der Anmeldung
|
||||||
Fahrzeug im Betrieb haben, zahlst Du sinnvollerweise auch
|
bei unserem Dienstleister Moqo verifiziert.
|
||||||
keinen Mitgliedsbeitrag. Es ist zur Zeit der 1.1.2025 als
|
|
||||||
Startdatum geplant.
|
|
||||||
|
|
||||||
Führerscheinverifikation: Weitere Informationen zur Verifikation
|
Bitte melde Dich nun noch auf der Webseite von Moqo für Deinen gewählten
|
||||||
deines Führerscheins folgen in Kürze. Du musst nichts weiter tun,
|
Tarif an:
|
||||||
wir werden uns bei dir melden, sobald es notwendig ist.
|
|
||||||
|
|
||||||
Moqo App:
|
Moqo Anmeldung(Bitte UNBEDINGT AUSFÜLLEN, sonst ist kein Ausleihen möglich):
|
||||||
Wir werden die Moqo App nutzen,
|
https://portal.moqo.de/js_sign_up/18013114#/subscription-selection
|
||||||
um das Fahrzeug ausleihen zu können. Wenn Du schon mal einen
|
|
||||||
ersten Eindruck von dem Buchungsvorgang haben möchtest,
|
Das Ausleihen:
|
||||||
schaue Dir gerne dieses kurze Video an:
|
Wir nutzen die Moqo App, damit Du das Fahrzeug mit Deinem Handy
|
||||||
|
entsperren und Deine Buchungen verwalten kannst.
|
||||||
|
Wenn Du schon mal einen ersten Eindruck von dem Buchungsvorgang
|
||||||
|
haben möchtest, schaue Dir gerne dieses kurze Video an:
|
||||||
Moqo App Nutzung
|
Moqo App Nutzung
|
||||||
|
|
||||||
https://www.youtube.com/shorts/ZMKUX0uyOps
|
https://www.youtube.com/shorts/ZMKUX0uyOps
|
||||||
|
|
||||||
Dörpsmobil:
|
Dörpsmobil:
|
||||||
Wir sind nicht alleine sondern Mitglied in einem Schleswig-Holstein
|
Wir sind nicht alleine sondern Mitglied in einem Schleswig-Holstein
|
||||||
weiten Netz an gemeinnützigen Carsharing Anbietern. Für mehr
|
weiten Netz an gemeinnützigen Carsharing Anbietern. Für mehr
|
||||||
@@ -49,8 +50,8 @@ du dich jederzeit an unsere Vorsitzende wenden:
|
|||||||
E-Mail: vorstand@carsharing-hasloh.de
|
E-Mail: vorstand@carsharing-hasloh.de
|
||||||
Telefon: +49 176 5013 4256
|
Telefon: +49 176 5013 4256
|
||||||
|
|
||||||
Wir danken dir herzlich für dein Vertrauen in uns und freuen uns
|
Wir danken Dir herzlich für Dein Vertrauen und freuen uns drauf, Dich bald
|
||||||
darauf, dich hoffentlich bald mit einem Auto begrüßen zu dürfen.
|
Dörpsmobil fahrend in Hasloh zu sehen!
|
||||||
|
|
||||||
Mit freundlichen Grüßen,
|
Mit freundlichen Grüßen,
|
||||||
Dein Carsharing Hasloh Team
|
Dein Carsharing Hasloh Team
|
||||||
|
|||||||
Reference in New Issue
Block a user