add checkbox styling, driverslicence handling and validation
This commit is contained in:
1
frontend/src/app.d.ts
vendored
1
frontend/src/app.d.ts
vendored
@@ -73,6 +73,7 @@ declare global {
|
|||||||
interface Locals {
|
interface Locals {
|
||||||
user: User;
|
user: User;
|
||||||
subscriptions: Subscription[];
|
subscriptions: Subscription[];
|
||||||
|
licence_categories: LicenceCategory[];
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export async function handle({ event, resolve }) {
|
|||||||
|
|
||||||
event.locals.subscriptions = data.subscriptions;
|
event.locals.subscriptions = data.subscriptions;
|
||||||
event.locals.user = data.user;
|
event.locals.user = data.user;
|
||||||
|
event.locals.licence_categories = data.licence_categories;
|
||||||
|
console.dir(event.locals.licence_categories);
|
||||||
if (event.locals.user.date_of_birth) {
|
if (event.locals.user.date_of_birth) {
|
||||||
event.locals.user.date_of_birth =
|
event.locals.user.date_of_birth =
|
||||||
event.locals.user.date_of_birth.split("T")[0];
|
event.locals.user.date_of_birth.split("T")[0];
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
export let toUpperCase = false;
|
export let toUpperCase = false;
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
export let checked = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Event} event - The input event
|
* @param {Event} event - The input event
|
||||||
*/
|
*/
|
||||||
@@ -97,6 +100,11 @@
|
|||||||
/^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value)
|
/^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value)
|
||||||
? null
|
? null
|
||||||
: $t("validation.bic");
|
: $t("validation.bic");
|
||||||
|
case "licence_number":
|
||||||
|
return typeof value === "string" && value.length == 11
|
||||||
|
? null
|
||||||
|
: $t("validation.drivers_licence");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return typeof value === "string" && !value.trim() && required
|
return typeof value === "string" && !value.trim() && required
|
||||||
? $t("validation.required")
|
? $t("validation.required")
|
||||||
@@ -109,8 +117,21 @@
|
|||||||
$: selectedColor = selectedOption ? selectedOption.color : "";
|
$: selectedColor = selectedOption ? selectedOption.color : "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="input-box">
|
<div class="input-box {type === 'checkbox' ? 'checkbox-container' : ''}">
|
||||||
|
{#if type === "checkbox"}
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
{name}
|
||||||
|
{value}
|
||||||
|
{checked}
|
||||||
|
on:change={() => (checked = !checked)}
|
||||||
|
/>
|
||||||
|
<span class="checkbox-text"> {label} </span>
|
||||||
|
</label>
|
||||||
|
{:else}
|
||||||
<span class="label">{label}</span>
|
<span class="label">{label}</span>
|
||||||
|
{/if}
|
||||||
<div class="input-error-container">
|
<div class="input-error-container">
|
||||||
{#if error}
|
{#if error}
|
||||||
<span class="error-message">{error}</span>
|
<span class="error-message">{error}</span>
|
||||||
@@ -137,7 +158,7 @@
|
|||||||
class="input textarea"
|
class="input textarea"
|
||||||
style="height:{rows * 1.5}em;"
|
style="height:{rows * 1.5}em;"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else if type != "checkbox"}
|
||||||
<input
|
<input
|
||||||
{name}
|
{name}
|
||||||
{type}
|
{type}
|
||||||
@@ -153,6 +174,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.checkbox-container {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
margin-right: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-text {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.checkbox-text {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
.select {
|
.select {
|
||||||
padding-right: 1.5em;
|
padding-right: 1.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
frontend/src/lib/img/Avatar-f3.jpeg
Normal file
BIN
frontend/src/lib/img/Avatar-f3.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
@@ -29,6 +29,10 @@ export default {
|
|||||||
bic: "BIC eingeben(Bei nicht deutschen Konten)...",
|
bic: "BIC eingeben(Bei nicht deutschen Konten)...",
|
||||||
mandate_reference: "SEPA Mandatsreferenz eingeben..",
|
mandate_reference: "SEPA Mandatsreferenz eingeben..",
|
||||||
notes: "Deine Notizen zu {name}...",
|
notes: "Deine Notizen zu {name}...",
|
||||||
|
licence_number: "Auf dem Führerschein unter Feld 5",
|
||||||
|
issued_date: "Ausgabedatum unter Feld 4a",
|
||||||
|
expiration_date: "Ablaufdatum unter Feld 4b",
|
||||||
|
issuing_country: "Ausstellendes Land",
|
||||||
},
|
},
|
||||||
validation: {
|
validation: {
|
||||||
required: "Eingabe benötigt",
|
required: "Eingabe benötigt",
|
||||||
@@ -40,6 +44,7 @@ export default {
|
|||||||
iban: "Ungültige IBAN",
|
iban: "Ungültige IBAN",
|
||||||
date: "Bitte geben Sie ein Datum ein",
|
date: "Bitte geben Sie ein Datum ein",
|
||||||
email: "Ungültige Emailadresse",
|
email: "Ungültige Emailadresse",
|
||||||
|
drivers_licence: "Nummer zu kurz(11 Zeichen)",
|
||||||
},
|
},
|
||||||
licenceCategory: {
|
licenceCategory: {
|
||||||
AM: "Mopeds und leichte vierrädrige Kraftfahrzeuge(50ccm,max 45km/h)",
|
AM: "Mopeds und leichte vierrädrige Kraftfahrzeuge(50ccm,max 45km/h)",
|
||||||
@@ -59,8 +64,13 @@ export default {
|
|||||||
L: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 40km/h",
|
L: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 40km/h",
|
||||||
T: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 60km/h",
|
T: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 60km/h",
|
||||||
},
|
},
|
||||||
|
licence_categories: "Führerscheinklassen",
|
||||||
subscription_model: "Mitgliedschatfsmodell",
|
subscription_model: "Mitgliedschatfsmodell",
|
||||||
licence: "Lizenz",
|
licence: "Führerschein",
|
||||||
|
licence_number: "Führerscheinnummer",
|
||||||
|
issued_date: "Ausgabedatum",
|
||||||
|
expiration_date: "Ablaufdatum",
|
||||||
|
country: "Land",
|
||||||
monthly_fee: "Monatliche Gebühr",
|
monthly_fee: "Monatliche Gebühr",
|
||||||
hourly_rate: "Stundensatz",
|
hourly_rate: "Stundensatz",
|
||||||
details: "Details",
|
details: "Details",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** @type {import('./$types').LayoutLoad} */
|
/** @type {import('./$types').LayoutLoad} */
|
||||||
export async function load({ fetch, url, data }) {
|
export async function load({ fetch, url, data }) {
|
||||||
const { user, subscriptions } = data;
|
const { user, subscriptions, licence_categories } = data;
|
||||||
return { fetch, url: url.pathname, user, subscriptions };
|
return { fetch, url: url.pathname, user, subscriptions, licence_categories };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ export async function load({ locals }) {
|
|||||||
return {
|
return {
|
||||||
user: locals.user,
|
user: locals.user,
|
||||||
subscriptions: locals.subscriptions,
|
subscriptions: locals.subscriptions,
|
||||||
|
licence_categories: locals.licence_categories,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
/** @type {App.Locals['user']}*/
|
/** @type {App.Locals['user']}*/
|
||||||
$: user = $page.data.user;
|
$: user = $page.data.user;
|
||||||
|
|
||||||
|
/** @type {App.Locals['licence_categories']} */
|
||||||
|
$: licence_categories = $page.data.licence_categories;
|
||||||
|
|
||||||
/** @typedef {{name: string, src: string}} Avatar */
|
/** @typedef {{name: string, src: string}} Avatar */
|
||||||
const avatarFiles = import.meta.glob("$lib/img/Avatar-*.jpeg", {
|
const avatarFiles = import.meta.glob("$lib/img/Avatar-*.jpeg", {
|
||||||
eager: true,
|
eager: true,
|
||||||
@@ -55,6 +58,7 @@
|
|||||||
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
|
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
|
||||||
];
|
];
|
||||||
const licenceStatusOptions = [
|
const licenceStatusOptions = [
|
||||||
|
{ value: 1, label: $t("userStatus.1"), color: "#b1b1b1" }, // Grey for "Nicht verifiziert"
|
||||||
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv"
|
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv"
|
||||||
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv"
|
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv"
|
||||||
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
|
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
|
||||||
@@ -78,6 +82,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
console.dir(user);
|
console.dir(user);
|
||||||
|
console.dir(licence_categories);
|
||||||
avatars = Object.entries(avatarFiles).map(([path, module]) => {
|
avatars = Object.entries(avatarFiles).map(([path, module]) => {
|
||||||
if (typeof path !== "string") {
|
if (typeof path !== "string") {
|
||||||
throw new Error("Unexpected non-string path");
|
throw new Error("Unexpected non-string path");
|
||||||
@@ -437,6 +442,58 @@
|
|||||||
bind:value={user.drivers_licence.status}
|
bind:value={user.drivers_licence.status}
|
||||||
options={licenceStatusOptions}
|
options={licenceStatusOptions}
|
||||||
/>
|
/>
|
||||||
|
<InputField
|
||||||
|
name="licence_number"
|
||||||
|
type="text"
|
||||||
|
label={$t("licencce_number")}
|
||||||
|
bind:value={user.drivers_licence.licence_number}
|
||||||
|
placeholder={$t("placeholder.licence_number")}
|
||||||
|
toUpperCase={true}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="isued_date"
|
||||||
|
type="date"
|
||||||
|
label={$t("issued_date")}
|
||||||
|
bind:value={user.drivers_licence.issued_date}
|
||||||
|
placeholder={$t("placeholder.issued_date")}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="expiration_date"
|
||||||
|
type="date"
|
||||||
|
label={$t("expiration_date")}
|
||||||
|
bind:value={user.drivers_licence.expiration_date}
|
||||||
|
placeholder={$t("placeholder.expiration_date")}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="country"
|
||||||
|
label={$t("country")}
|
||||||
|
bind:value={user.drivers_licence.issuing_country}
|
||||||
|
placeholder={$t("placeholder.issuing_country")}
|
||||||
|
/>
|
||||||
|
<div class="licence-categories">
|
||||||
|
<h3>{$t("licence_categories")}</h3>
|
||||||
|
<div class="checkbox-grid">
|
||||||
|
{#each licence_categories as category}
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<div class="checkbox-label-container">
|
||||||
|
<InputField
|
||||||
|
type="checkbox"
|
||||||
|
name="licence_categories[]"
|
||||||
|
value={category.category}
|
||||||
|
label={category.category}
|
||||||
|
checked={user.drivers_licence.licence_categories != null &&
|
||||||
|
user.drivers_licence.licence_categories.some(
|
||||||
|
(cat) => cat.category === category.category
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="checkbox-description">
|
||||||
|
{$t(`licenceCategory.${category.category}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tab-content"
|
class="tab-content"
|
||||||
@@ -566,6 +623,54 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.licence-categories {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label-container {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-description {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.checkbox-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.checkbox-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-description {
|
||||||
|
margin-left: 24px;
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
.subscription-info {
|
.subscription-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type UserController struct {
|
|||||||
ConsentService services.ConsentServiceInterface
|
ConsentService services.ConsentServiceInterface
|
||||||
BankAccountService services.BankAccountServiceInterface
|
BankAccountService services.BankAccountServiceInterface
|
||||||
MembershipService services.MembershipServiceInterface
|
MembershipService services.MembershipServiceInterface
|
||||||
|
DriversLicenceService services.DriversLicenceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationData struct {
|
type RegistrationData struct {
|
||||||
@@ -129,10 +130,17 @@ func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving subscriptions."})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving subscriptions."})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
licenceCategories, err := uc.DriversLicenceService.GetAllCategories()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("Error retrieving licence categories: %v", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving licence categories."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Error.Printf("licenceCategories: %#v", licenceCategories)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"user": user.Safe(),
|
"user": user.Safe(),
|
||||||
"subscriptions": subscriptions,
|
"subscriptions": subscriptions,
|
||||||
|
"licence_categories": licenceCategories,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
@@ -482,7 +482,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
||||||
category, err := licenceRepo.FindCategoryByName("B")
|
category, err := licenceRepo.FindCategoryByName("B")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -500,7 +500,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
||||||
category, err := licenceRepo.FindCategoryByName("B")
|
category, err := licenceRepo.FindCategoryByName("B")
|
||||||
category2, err := licenceRepo.FindCategoryByName("BE")
|
category2, err := licenceRepo.FindCategoryByName("BE")
|
||||||
@@ -519,7 +519,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.FirstName = "John Updated"
|
u.FirstName = "John Updated"
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
u.DriversLicence.LicenceCategories = []models.LicenceCategory{}
|
u.DriversLicence.LicenceCategories = []models.LicenceCategory{}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
@@ -534,7 +534,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.ID = 1
|
u.ID = 1
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
u.FirstName = "John Missing ID"
|
u.FirstName = "John Missing ID"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusForbidden,
|
expectedStatus: http.StatusForbidden,
|
||||||
@@ -549,7 +549,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.Password = ""
|
u.Password = ""
|
||||||
u.LastName = "Doe Updated"
|
u.LastName = "Doe Updated"
|
||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "NEWNUMBER"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
u.Password = "NewPassword"
|
u.Password = "NewPassword"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
@@ -652,6 +652,9 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
assert.Equal(t, updatedUser.Membership.SubscriptionModelID, updatedUserFromDB.Membership.SubscriptionModelID, "Membership.SubscriptionModelID mismatch")
|
assert.Equal(t, updatedUser.Membership.SubscriptionModelID, updatedUserFromDB.Membership.SubscriptionModelID, "Membership.SubscriptionModelID 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.DriversLicence.Status == 0 {
|
||||||
|
updatedUser.DriversLicence.Status = constants.UnverifiedStatus
|
||||||
|
}
|
||||||
assert.Equal(t, updatedUser.DriversLicence.Status, updatedUserFromDB.DriversLicence.Status, "DriversLicence.Status mismatch")
|
assert.Equal(t, updatedUser.DriversLicence.Status, updatedUserFromDB.DriversLicence.Status, "DriversLicence.Status mismatch")
|
||||||
assert.Equal(t, updatedUser.DriversLicence.LicenceNumber, updatedUserFromDB.DriversLicence.LicenceNumber, "DriversLicence.LicenceNumber mismatch")
|
assert.Equal(t, updatedUser.DriversLicence.LicenceNumber, updatedUserFromDB.DriversLicence.LicenceNumber, "DriversLicence.LicenceNumber mismatch")
|
||||||
assert.Equal(t, updatedUser.DriversLicence.IssuedDate, updatedUserFromDB.DriversLicence.IssuedDate, "DriversLicence.IssuedDate mismatch")
|
assert.Equal(t, updatedUser.DriversLicence.IssuedDate, updatedUserFromDB.DriversLicence.IssuedDate, "DriversLicence.IssuedDate mismatch")
|
||||||
@@ -1023,5 +1026,27 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
return user
|
return user
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "wrong driverslicence number, should fail",
|
||||||
|
WantResponse: http.StatusNotAcceptable,
|
||||||
|
WantDBData: map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
|
||||||
|
Assert: false,
|
||||||
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
|
user.Email = "john.wronglicence.doe@example.com"
|
||||||
|
user.DriversLicence.LicenceNumber = "AAAA12345AA"
|
||||||
|
return user
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Correct DriversLicence number, should pass",
|
||||||
|
WantResponse: http.StatusCreated,
|
||||||
|
WantDBData: map[string]interface{}{"email": "john.correctLicenceNumber@example.com"},
|
||||||
|
Assert: true,
|
||||||
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
|
user.Email = "john.correctLicenceNumber@example.com"
|
||||||
|
user.DriversLicence.LicenceNumber = "B072RRE2I55"
|
||||||
|
return user
|
||||||
|
})),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,15 @@ import (
|
|||||||
|
|
||||||
type DriversLicence struct {
|
type DriversLicence struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Status int8 `json:"licence_status" validate:"omitempty,number"`
|
Status int8 `json:"status" validate:"omitempty,number"`
|
||||||
LicenceNumber string `json:"licence_number" validate:"safe_content"`
|
LicenceNumber string `json:"number" validate:"omitempty,euDriversLicence,safe_content"`
|
||||||
IssuedDate time.Time `json:"licence_issued_date" validate:"omitempty,lte"`
|
IssuedDate time.Time `json:"issued_date" validate:"omitempty,lte"`
|
||||||
ExpirationDate time.Time `json:"licence_expiration_date" validate:"omitempty,gt"`
|
ExpirationDate time.Time `json:"expiration_date" validate:"omitempty,gt"`
|
||||||
IssuingCountry string `json:"licence_country" validate:"safe_content"`
|
IssuingCountry string `json:"country" validate:"safe_content"`
|
||||||
LicenceCategories []LicenceCategory `json:"licence_categories" gorm:"many2many:licence_2_categories"`
|
LicenceCategories []LicenceCategory `json:"licence_categories" gorm:"many2many:licence_2_categories"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LicenceCategory struct {
|
type LicenceCategory struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Category string `json:"licence_category" validate:"safe_content"`
|
Category string `json:"category" validate:"safe_content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (d *DriversLicence) BeforeCreate(tx *gorm.DB) (err error) {
|
|
||||||
// d.Status = constants.UnverifiedStatus
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -98,5 +98,13 @@ func (u *User) Safe() map[string]interface{} {
|
|||||||
"bic": u.BankAccount.BIC,
|
"bic": u.BankAccount.BIC,
|
||||||
"mandate_reference": u.BankAccount.MandateReference,
|
"mandate_reference": u.BankAccount.MandateReference,
|
||||||
},
|
},
|
||||||
|
"drivers_licence": map[string]interface{}{
|
||||||
|
"id": u.DriversLicence.ID,
|
||||||
|
"status": u.DriversLicence.Status,
|
||||||
|
"issued_date": u.DriversLicence.IssuedDate,
|
||||||
|
"expiration_date": u.DriversLicence.ExpirationDate,
|
||||||
|
"country": u.DriversLicence.IssuingCountry,
|
||||||
|
"licence_categories": u.DriversLicence.LicenceCategories,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,16 @@ import (
|
|||||||
type DriversLicenceInterface interface {
|
type DriversLicenceInterface interface {
|
||||||
FindCategoryByName(categoryName string) (models.LicenceCategory, error)
|
FindCategoryByName(categoryName string) (models.LicenceCategory, error)
|
||||||
FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error)
|
FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error)
|
||||||
|
GetAllCategories() ([]models.LicenceCategory, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DriversLicenceRepository struct{}
|
type DriversLicenceRepository struct{}
|
||||||
|
|
||||||
|
func (r *DriversLicenceRepository) GetAllCategories() ([]models.LicenceCategory, error) {
|
||||||
|
var categories []models.LicenceCategory
|
||||||
|
err := database.DB.Find(&categories).Error
|
||||||
|
return categories, err
|
||||||
|
}
|
||||||
func (r *DriversLicenceRepository) FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error) {
|
func (r *DriversLicenceRepository) FindCategoriesByIDs(ids []uint) ([]models.LicenceCategory, error) {
|
||||||
var categories []models.LicenceCategory
|
var categories []models.LicenceCategory
|
||||||
err := database.DB.Where("id IN ?", ids).Find(&categories).Error
|
err := database.DB.Where("id IN ?", ids).Find(&categories).Error
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ func Run() {
|
|||||||
|
|
||||||
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
||||||
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
||||||
|
driversLicenceService := &services.DriversLicenceService{Repo: licenceRepo}
|
||||||
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
||||||
|
|
||||||
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, DriversLicenceService: driversLicenceService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
||||||
membershipController := &controllers.MembershipController{Service: *membershipService}
|
membershipController := &controllers.MembershipController{Service: *membershipService}
|
||||||
|
|
||||||
contactController := &controllers.ContactController{EmailService: emailService}
|
contactController := &controllers.ContactController{EmailService: emailService}
|
||||||
|
|||||||
18
internal/services/drivers_licence_service.go
Normal file
18
internal/services/drivers_licence_service.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/repositories"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DriversLicenceInterface interface {
|
||||||
|
GetAllCategories() ([]models.LicenceCategory, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DriversLicenceService struct {
|
||||||
|
Repo repositories.DriversLicenceInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DriversLicenceService) GetAllCategories() ([]models.LicenceCategory, error) {
|
||||||
|
return s.Repo.GetAllCategories()
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ type UserService struct {
|
|||||||
func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) {
|
func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) {
|
||||||
|
|
||||||
if err := validateUserData(user, userRole); err != nil {
|
if err := validateUserData(user, userRole); err != nil {
|
||||||
|
logger.Info.Printf("UPDATING user: %#v", user)
|
||||||
logger.Error.Printf("Failed to validate user data: %v", err)
|
logger.Error.Printf("Failed to validate user data: %v", err)
|
||||||
return nil, errors.ErrInvalidUserData
|
return nil, errors.ErrInvalidUserData
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,10 @@ func (service *UserService) UpdateUser(user *models.User, userRole int8) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.UpdatedAt = time.Now()
|
user.UpdatedAt = time.Now()
|
||||||
|
if user.DriversLicence.Status == 0 {
|
||||||
|
// This is a new drivers licence
|
||||||
|
user.DriversLicence.Status = constants.UnverifiedStatus
|
||||||
|
}
|
||||||
|
|
||||||
updatedUser, err := service.Repo.UpdateUser(user)
|
updatedUser, err := service.Repo.UpdateUser(user)
|
||||||
|
|
||||||
@@ -71,6 +76,7 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
|
|||||||
user.CreatedAt = time.Now()
|
user.CreatedAt = time.Now()
|
||||||
user.UpdatedAt = time.Now()
|
user.UpdatedAt = time.Now()
|
||||||
user.PaymentStatus = constants.AwaitingPaymentStatus
|
user.PaymentStatus = constants.AwaitingPaymentStatus
|
||||||
|
// user.DriversLicence.Status = constants.UnverifiedStatus
|
||||||
user.BankAccount.MandateDateSigned = time.Now()
|
user.BankAccount.MandateDateSigned = time.Now()
|
||||||
id, err := service.Repo.CreateUser(user)
|
id, err := service.Repo.CreateUser(user)
|
||||||
|
|
||||||
@@ -162,13 +168,14 @@ func validateUserData(user *models.User, userRole int8) error {
|
|||||||
validate.RegisterValidation("bic", utils.ValidateToTrue)
|
validate.RegisterValidation("bic", utils.ValidateToTrue)
|
||||||
validate.RegisterValidation("subscriptionModel", utils.ValidateToTrue)
|
validate.RegisterValidation("subscriptionModel", utils.ValidateToTrue)
|
||||||
validate.RegisterValidation("iban", utils.ValidateToTrue)
|
validate.RegisterValidation("iban", utils.ValidateToTrue)
|
||||||
|
validate.RegisterValidation("euDriversLicence", utils.ValidateToTrue)
|
||||||
} else {
|
} else {
|
||||||
validate.RegisterValidation("membershipField", utils.ValidateRequiredMembershipField)
|
validate.RegisterValidation("membershipField", utils.ValidateRequiredMembershipField)
|
||||||
validate.RegisterValidation("age", utils.AgeValidator)
|
validate.RegisterValidation("age", utils.AgeValidator)
|
||||||
validate.RegisterValidation("bic", utils.BICValidator)
|
validate.RegisterValidation("bic", utils.BICValidator)
|
||||||
validate.RegisterValidation("subscriptionModel", utils.SubscriptionModelValidator)
|
validate.RegisterValidation("subscriptionModel", utils.SubscriptionModelValidator)
|
||||||
validate.RegisterValidation("iban", utils.IBANValidator)
|
validate.RegisterValidation("iban", utils.IBANValidator)
|
||||||
|
validate.RegisterValidation("euDriversLicence", utils.ValidateDriversLicence)
|
||||||
}
|
}
|
||||||
return validate.Struct(user)
|
return validate.Struct(user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -80,22 +81,16 @@ func ValidateRequiredMembershipField(fl validator.FieldLevel) bool {
|
|||||||
// Get the value of the field specified by RequiredMembershipField
|
// Get the value of the field specified by RequiredMembershipField
|
||||||
fieldValue := reflect.ValueOf(membership).FieldByName(fieldName)
|
fieldValue := reflect.ValueOf(membership).FieldByName(fieldName)
|
||||||
|
|
||||||
logger.Info.Printf("Starting fieldValue Validation for %v: %#v", fieldName, fieldValue)
|
|
||||||
|
|
||||||
// Check if the fieldValue is valid
|
// Check if the fieldValue is valid
|
||||||
if !fieldValue.IsValid() {
|
if !fieldValue.IsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Printf("fieldValue is valid: %#v", fieldValue)
|
|
||||||
|
|
||||||
// Check if the fieldValue is a nil pointer
|
// Check if the fieldValue is a nil pointer
|
||||||
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Println("fieldValue is not a nil pointer")
|
|
||||||
|
|
||||||
// Ensure that the fieldValue is an uint
|
// Ensure that the fieldValue is an uint
|
||||||
var fieldUint uint
|
var fieldUint uint
|
||||||
if fieldValue.Kind() == reflect.Uint {
|
if fieldValue.Kind() == reflect.Uint {
|
||||||
@@ -104,17 +99,12 @@ func ValidateRequiredMembershipField(fl validator.FieldLevel) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Println("fieldValue is a int")
|
|
||||||
|
|
||||||
var membershipIDs []uint
|
var membershipIDs []uint
|
||||||
if err := database.DB.Model(&models.Membership{}).Pluck("id", &membershipIDs).Error; err != nil {
|
if err := database.DB.Model(&models.Membership{}).Pluck("id", &membershipIDs).Error; err != nil {
|
||||||
logger.Error.Fatalf("Couldn't get SubscriptionModel names: %#v", err)
|
logger.Error.Fatalf("Couldn't get SubscriptionModel names: %#v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Printf("found ids of members: %#v", membershipIDs)
|
|
||||||
|
|
||||||
// logger.Info.Printf("FIELD_NAME: %#v\nVALUE: %#v", fieldName, fieldValue)
|
|
||||||
// Check if the field value is zero (empty)
|
// Check if the field value is zero (empty)
|
||||||
return slices.Contains(membershipIDs, fieldUint)
|
return slices.Contains(membershipIDs, fieldUint)
|
||||||
}
|
}
|
||||||
@@ -134,3 +124,34 @@ func ValidateSafeContent(fl validator.FieldLevel) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateDriversLicence(fl validator.FieldLevel) bool {
|
||||||
|
fieldValue := fl.Field().String()
|
||||||
|
if len(fieldValue) != 11 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
id, tenthChar := string(fieldValue[:9]), string(fieldValue[9])
|
||||||
|
|
||||||
|
if tenthChar == "X" {
|
||||||
|
tenthChar = "10"
|
||||||
|
}
|
||||||
|
tenthValue, _ := strconv.ParseInt(tenthChar, 10, 8)
|
||||||
|
|
||||||
|
// for readability
|
||||||
|
weights := []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||||
|
sum := 0
|
||||||
|
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
char := string(id[i])
|
||||||
|
value, _ := strconv.ParseInt(char, 36, 64)
|
||||||
|
sum += int(value) * weights[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
calcCheckDigit := sum % 11
|
||||||
|
if calcCheckDigit != int(tenthValue) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user