Skip to content

Commit

Permalink
✨ 57 feature user information (#58)
Browse files Browse the repository at this point in the history
* extracted common information into seperate component

* Added downloadable file for agb accept component

* Added input fields for seller information

* Added constant for maximum title length

* updated input fields for common information

* Added icons infront of the inputs

* updated responsivnes

* added added all information steps for editing ad

* added subtitle to common card

* changed type of eventbus to adTo

* added disable mechanism for all inputs

* reworked api with template

* 🍻 tested new sso api

* 🍻 tested new sso api

* 💩

* corrected snackbar

* better readability

* added userId and computed properties to user store

* added api calls for user to navbar

* 💩 needs removal of api calls

* added authentication to api-specs

* regenerated api files

* exported function for xsrf-token to be used in the composable

* cleaned up api loagic an introduced loading animation

* sanitized inputed username

* 🎨 linted and reformated code

* Fix code scanning alert no. 66: Log Injection

better sanitize

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: jannik.lange <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent 5cce51f commit d7c6b00
Show file tree
Hide file tree
Showing 26 changed files with 742 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import de.muenchen.anzeigenportal.swbrett.users.model.SwbUserTO;
import de.muenchen.anzeigenportal.swbrett.users.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@Slf4j
@RestController
@RequestMapping("/users")
public class UserController {
Expand All @@ -24,18 +26,17 @@ public SwbUserTO getUser(@PathVariable("userId") final long userId) {
/**
* Put Method to encrypt the email with https.
*
* @param email
* @param lhmObjectId unique description in keycloak for every user
* @return SwbUserTO with id, if user found. id = null if no user found
*/
@PutMapping("/find")
@ResponseStatus(HttpStatus.OK)
public SwbUserTO findUser(@RequestBody final String email) {
final Optional<SwbUserTO> userTO = service.findUser(email);
if (userTO.isPresent()) {
return userTO.get();
} else {
return new SwbUserTO();
}
public SwbUserTO findUser(@RequestBody final String lhmObjectId) {
String sanitizedId = lhmObjectId.replace("\"", ""); // Entfernt Anführungszeichen
sanitizedId = sanitizedId.replaceAll("[\\r\\n]", ""); // Entfernt Zeilenumbrüche
final Optional<SwbUserTO> userTO = service.findUser(sanitizedId);
log.debug("CONTROLLER | findUser with lhmObjectID: {} was: {}", sanitizedId, userTO.isPresent());
return userTO.orElseGet(SwbUserTO::new);
}

@PostMapping()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ public SwbUserTO getUser(final long id) {
public Optional<SwbUserTO> findUser(final String lhmObjectId) {
final Optional<SwbUser> user = repository.findByLhmObjectId(lhmObjectId);

if (user.isPresent()) {
return Optional.of(mapper.toSwbUserTO(user.get()));
} else {
return Optional.empty();
}
return user.map(swbUser -> mapper.toSwbUserTO(swbUser));
}

public SwbUserTO createUser(final SwbUserTO userTO) {
Expand Down
5 changes: 5 additions & 0 deletions anzeigen-frontend/src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ export const EV_SNACKBAR = "eventbus-snackbar";
*/
export const API_ERROR_MSG =
"Ein Fehler ist aufgetreten. Bitte aktualisieren Sie die Seite oder versuchen Sie es später erneut.";

/**
* Other constants
*/
export const AD_MAX_TITLE_LENGTH = 40;
4 changes: 2 additions & 2 deletions anzeigen-frontend/src/api/fetch-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function defaultCatchHandler(
* Builds the headers for the request.
* @returns {Headers}
*/
function getHeaders(): Headers {
export function getHeaders(): Headers {
const headers = new Headers({
"Content-Type": "application/json",
});
Expand All @@ -136,7 +136,7 @@ function getHeaders(): Headers {
* Returns the XSRF-TOKEN.
* @returns {string|string}
*/
function getXSRFToken(): string {
export function getXSRFToken(): string {
const help = document.cookie.match(
"(^|;)\\s*" + "XSRF-TOKEN" + "\\s*=\\s*([^;]+)"
);
Expand Down
1 change: 0 additions & 1 deletion anzeigen-frontend/src/api/swbrett/.openapi-generator/FILES
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.openapi-generator-ignore
apis/DefaultApi.ts
apis/index.ts
index.ts
Expand Down
4 changes: 2 additions & 2 deletions anzeigen-frontend/src/api/user-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import User from "@/types/User";
*
* API-Definition (internal only): https://wiki.muenchen.de/betriebshandbuch/wiki/Red_Hat_Single_Sign-On_(Keycloak)#Scopes
*/
export function getUser(): Promise<User> {
return fetch("api/sso/userinfo", getConfig())
export async function getUser(): Promise<User> {
return await fetch("api/sso/userinfo", getConfig())
.catch(defaultCatchHandler)
.then((response) => {
defaultResponseHandler(
Expand Down
51 changes: 46 additions & 5 deletions anzeigen-frontend/src/components/Ad/Edit/AdAgbAccept.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,62 @@
<v-checkbox
v-model="isChecked"
color="accent"
:disabled="disabled"
>
<template #label>
<p>
Ich bin mit den <a>Nutzungsbedingungen (AGB)</a> einverstanden. Das
Ich bin mit den
<a :href="agbFile">Nutzungsbedingungen (AGB)</a> einverstanden. Das
Schwarze Brett darf nur zu rein privaten Zwecken genutzt werden.
Insbesondere Werbung kommerzieller Art ist nicht gestattet!
Informationen zur Verarbeitung personenbezogener Daten enthalten die
<a>Datenschutzhinweise</a>.
<a :href="securityFile">Datenschutzhinweise</a>.
</p>
</template>
</v-checkbox>
</template>

<script setup lang="ts">
const isChecked = defineModel<boolean>();
</script>
import { useObjectUrl } from "@vueuse/core";
import { onMounted, ref } from "vue";
import AGB from "@/static/AGB-1.pdf";
import SecInfo from "@/static/Datenschutzhinweise.pdf";
/**
* Value if checkbox is checked
*/
const isChecked = defineModel<boolean>({ default: false });
/**
* Disables the checkbox input
*/
defineProps<{
disabled?: boolean;
}>();
<style scoped></style>
/**
* Blob and objectUrl for AGB
*/
const agbBlob = ref();
const agbFile = useObjectUrl(agbBlob);
/**
* Blob and objectUrl for Datenschutzhinweise
*/
const securityBlob = ref();
const securityFile = useObjectUrl(securityBlob);
/**
* Load files on mount
*/
onMounted(async () => {
const [agbResponse, secResponse] = await Promise.all([
fetch(AGB),
fetch(SecInfo),
]);
agbBlob.value = await agbResponse.blob();
securityBlob.value = await secResponse.blob();
});
</script>
140 changes: 130 additions & 10 deletions anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
<v-dialog
v-model="dialog"
persistent
max-width="1400px"
max-width="900px"
>
<v-card>
<v-card-title>
<v-container class="mx-0 ad-max-width">
<v-row>
<p>Anzeige erstellen oder bearbeiten</p>
<p v-if="isAdCreate">Anzeige erstellen</p>
<p v-else>Anzeige bearbeiten</p>
<v-spacer />
<v-btn
prepend-icon="mdi-window-close"
Expand All @@ -20,17 +21,41 @@
</v-container>
</v-card-title>
<v-card-text>
<ad-edit-stepper />
<v-form v-model="form">
<ad-display-card>
<template #subtitle> Allgemeine Informationen </template>
<template #text>
<common-ad-information :disabled="disabledInputs" />
</template>
</ad-display-card>
<v-divider />
<ad-display-card>
<template #subtitle> Optionale Informationen </template>
<template #text>
<optional-ad-information :disabled="disabledInputs" />
</template>
</ad-display-card>
<v-divider />
<ad-display-card>
<template #subtitle> Verkäufer Informationen </template>
<template #text>
<seller-ad-information :disabled="disabledInputs" />
</template>
</ad-display-card>
</v-form>
</v-card-text>
<v-card-actions>
<v-card-actions class="px-4">
<v-btn
variant="elevated"
color="accent"
prepend-icon="mdi-content-save-outline"
@click="createAd"
>
Erstellen / Speichern
<p v-if="isAdCreate">Erstellen</p>
<p v-else>Speichern</p>
</v-btn>
<v-btn
v-if="!isAdCreate"
variant="elevated"
color="error"
prepend-icon="mdi-trash-can-outline"
Expand All @@ -43,19 +68,114 @@
</template>

<script setup lang="ts">
import type {
AdCategory,
AdTO,
SwbFileTO,
SwbImageTO,
SwbUserTO,
} from "@/api/swbrett";
import { useEventBus } from "@vueuse/core";
import { ref } from "vue";
import { computed, ref } from "vue";
import AdEditStepper from "@/components/Ad/Edit/AdEditStepper.vue";
import CommonAdInformation from "@/components/Ad/Edit/CommonAdInformation.vue";
import OptionalAdInformation from "@/components/Ad/Edit/OptionalAdInformation.vue";
import SellerAdInformation from "@/components/Ad/Edit/SellerAdInformation.vue";
import AdDisplayCard from "@/components/common/AdDisplayCard.vue";
import {
useCreateAd,
useDeleteAd,
useUpdateAd,
} from "@/composables/api/useAdApi";
import { useCreateUser } from "@/composables/api/useUserApi";
import { EV_EDIT_AD_DIALOG } from "@/Constants";
const dialog = ref(false);
const {
data: updateAdData,

Check failure on line 95 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'updateAdData' is assigned a value but never used
call: updateAdCall,

Check failure on line 96 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'updateAdCall' is assigned a value but never used
loading: updateAdLoading,

Check failure on line 97 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'updateAdLoading' is assigned a value but never used
error: updateAdError,

Check failure on line 98 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'updateAdError' is assigned a value but never used
} = useUpdateAd();
const {
call: deleteAdCall,

Check failure on line 101 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'deleteAdCall' is assigned a value but never used
loading: deleteAdLoading,

Check failure on line 102 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'deleteAdLoading' is assigned a value but never used
error: deleteAdError,

Check failure on line 103 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'deleteAdError' is assigned a value but never used
} = useDeleteAd();
const {
data: createAdData,

Check failure on line 106 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'createAdData' is assigned a value but never used
call: createAdCall,

Check failure on line 107 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'createAdCall' is assigned a value but never used
loading: createAdLoading,

Check failure on line 108 in anzeigen-frontend/src/components/Ad/Edit/AdDialog.vue

View workflow job for this annotation

GitHub Actions / build (anzeigen-frontend)

'createAdLoading' is assigned a value but never used
error: createAdError,
} = useCreateAd();
const { data, call, loading, error } = useCreateUser();
const dialog = ref<boolean>(false);
const adTo = ref<AdTO>();
const disabledInputs = ref<boolean>(false);
const dialogBus = useEventBus<boolean>(EV_EDIT_AD_DIALOG);
const dialogBus = useEventBus<AdTO>(EV_EDIT_AD_DIALOG);
dialogBus.on((event: boolean) => (dialog.value = event));
const form = ref<boolean>();
dialogBus.on((event: AdTO) => {
dialog.value = true;
adTo.value = event;
});
const exampleAd: AdTO = {
id: 1,
swbUser: {
id: 123,
name: "John Doe",
email: "[email protected]",
} as SwbUserTO,
adCategory: {
id: 10,
name: "Electronics",
} as AdCategory,
adType: "SEEK", // Beispielwert aus AdTOAdTypeEnum
active: true,
title: "Smartphone for Sale",
description: "A lightly used smartphone in excellent condition.",
price: 250,
phone: "+123456789",
email: "[email protected]",
link: "https://example.com/listing/1",
creationDateTime: new Date("2024-01-01T12:00:00Z"),
expiryDate: new Date("2024-12-31T23:59:59Z"),
imagePreviewBase64: "data:image/png;base64,iVBORw0KGgoAAAANS...",
adImg: {
id: 101,
fileName: "smartphone.png",
url: "https://example.com/images/smartphone.png",
} as SwbImageTO,
adFiles: [
{
id: 201,
fileName: "manual.pdf",
url: "https://example.com/files/manual.pdf",
} as SwbFileTO,
],
views: 150,
};
const createAd = () => {
call({
swbUserTO: {
displayName: "user",
id: 4,
lhmObjectId: "idontknow",
},
});
};
const close = () => (dialog.value = false);
const isAdCreate = computed(() => adTo.value === undefined);
</script>

<style scoped></style>
20 changes: 3 additions & 17 deletions anzeigen-frontend/src/components/Ad/Edit/AdEditStepper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,7 @@
<v-stepper-window>
<v-form>
<v-stepper-window-item value="1">
<v-text-field label="Titel" />
<v-select />
<v-radio-group inline>
<v-radio label="Biete" />
<v-radio label="Suche" />
</v-radio-group>
<v-textarea label="Beschreibung" />
<v-number-input
label="Preis"
control-variant="split"
/>
<v-radio-group inline>
<v-radio label="VHB" />
<v-radio label="Festpreis" />
<v-radio label="zu verschenken" />
</v-radio-group>
<common-ad-information />
</v-stepper-window-item>
<v-stepper-window-item value="2">
<v-file-input label="Foto auswählen" />
Expand Down Expand Up @@ -74,9 +59,10 @@
</template>

<script setup lang="ts">
import { VDateInput, VNumberInput } from "vuetify/labs/components";
import { VDateInput } from "vuetify/labs/components";
import AdAgbAccept from "@/components/Ad/Edit/AdAgbAccept.vue";
import CommonAdInformation from "@/components/Ad/Edit/CommonAdInformation.vue";
</script>

<style scoped></style>
Loading

0 comments on commit d7c6b00

Please sign in to comment.