Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd28303eb0 |
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Matthias Bremer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -44,9 +44,4 @@ target/buerokalender-1.1.0-runner
|
||||
Push to docker.io:
|
||||
```shell script
|
||||
./mvnw clean package -DskipTests -Dquarkus.container-image.push=true
|
||||
```
|
||||
|
||||
## Set up
|
||||
|
||||
Initial Admin-User $BASE_URL/user/init
|
||||
|
||||
```
|
||||
7
TODO.md
7
TODO.md
@@ -1,7 +0,0 @@
|
||||
# TODO
|
||||
|
||||
- [ ] AJAX via unpoly
|
||||
- [ ] Feiertage-WS mit Cache
|
||||
- [ ] REST-API via API-Key
|
||||
- [ ] Verkehrsmodul via Google-Maps: E-Mail, Telegram
|
||||
- [ ] CalDAV-Sync
|
||||
12
pom.xml
12
pom.xml
@@ -14,7 +14,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
|
||||
<quarkus.platform.version>2.2.2.Final</quarkus.platform.version>
|
||||
<quarkus.platform.version>2.0.0.Final</quarkus.platform.version>
|
||||
<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
@@ -75,7 +75,15 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-openapi</artifactId>
|
||||
<artifactId>quarkus-rest-client-jsonb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jsonp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package de.mbremer.extension;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import de.mbremer.secutity.User;
|
||||
import io.quarkus.qute.TemplateExtension;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -26,8 +24,4 @@ public class CommonExtensions {
|
||||
public static String rightPad(String str, int length) {
|
||||
return String.format("%1$-" + length + "s", str);
|
||||
}
|
||||
|
||||
public static String selectedIfIn(User user, Room room) {
|
||||
return user !=null && user.getRoom() != null && user.getRoom().getName().equals(room.getName()) ? "selected" : "";
|
||||
}
|
||||
}
|
||||
|
||||
23
src/main/java/de/mbremer/holiday/HolidayRestClient.java
Normal file
23
src/main/java/de/mbremer/holiday/HolidayRestClient.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package de.mbremer.holiday;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import javax.json.JsonObject;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* curl -H "X-DFA-Token: dfa" -X POST https://deutsche-feiertage-api.de/api/v1/2018-10-03
|
||||
*/
|
||||
@Path("/api/v1")
|
||||
@RegisterRestClient
|
||||
public interface HolidayRestClient {
|
||||
|
||||
/**
|
||||
* @param date yyyy-MM-DD
|
||||
*/
|
||||
@POST
|
||||
@Path("/{date}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
JsonObject getHoliday(@PathParam("date") String date, @HeaderParam("X-DFA-Token") String token);
|
||||
}
|
||||
32
src/main/java/de/mbremer/holiday/HolidayService.java
Normal file
32
src/main/java/de/mbremer/holiday/HolidayService.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package de.mbremer.holiday;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.json.JsonObject;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@ApplicationScoped
|
||||
public class HolidayService {
|
||||
|
||||
private static final String TOKEN = "dfa";
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
HolidayRestClient holidayRestClient;
|
||||
|
||||
/**
|
||||
* @return Holiday name or null if it's not a holiday;
|
||||
*/
|
||||
public String getHolidayNiedersachsen(LocalDate date) {
|
||||
// TODO Token via Filter
|
||||
// TODO Fehlertolerant
|
||||
// TODO Caching
|
||||
// TODO Test via Wiremock
|
||||
JsonObject holiday = holidayRestClient.getHoliday(date.format(DateTimeFormatter.ISO_DATE), TOKEN);
|
||||
return holiday.getBoolean("result", false) && holiday.getJsonObject("holiday").getJsonObject("regions").getBoolean("ni", false) ?
|
||||
holiday.getJsonObject("holiday").getString("name") : null;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package de.mbremer.image;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
public class Image extends PanacheEntity {
|
||||
@NotBlank
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
private byte[] data;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package de.mbremer.image;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.transaction.Transactional;
|
||||
import javax.validation.ValidationException;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ImageService {
|
||||
|
||||
private static final String ROOMPLAN_NAME = "roomplan";
|
||||
private static final int MAX_PLANSIZE_IN_BYTES = 1000 * 1024;
|
||||
|
||||
@Inject
|
||||
Logger log;
|
||||
|
||||
public byte[] getRoomplan() {
|
||||
try {
|
||||
Image image = Image.find("name", ROOMPLAN_NAME).singleResult();
|
||||
return image.getData();
|
||||
} catch (NoResultException e) {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private void validate(byte[] data) {
|
||||
if (data.length > MAX_PLANSIZE_IN_BYTES) {
|
||||
throw new ValidationException("Bild ist zu groß: " + data.length + " Bytes. Maximal " + MAX_PLANSIZE_IN_BYTES + " Bytes " +
|
||||
"erlaubt.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Transactional
|
||||
public void persistRoomplan(byte[] data) {
|
||||
validate(data);
|
||||
boolean exists = Image.count("name", ROOMPLAN_NAME) > 0;
|
||||
log.info("Persist Roomplan:" + data.length + " Bytes" );
|
||||
|
||||
if (exists) {
|
||||
Image.update("data=?1 where name=?2", data, ROOMPLAN_NAME);
|
||||
} else {
|
||||
Image roomplan = new Image(ROOMPLAN_NAME, data);
|
||||
roomplan.persist();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.mbremer.kalender;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import de.mbremer.secutity.User;
|
||||
import de.mbremer.secutity.UserService;
|
||||
import io.quarkus.qute.Location;
|
||||
@@ -10,6 +9,7 @@ import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.transaction.Transactional;
|
||||
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Path("/kalender")
|
||||
@RolesAllowed({"USER", "ADMIN"})
|
||||
@ApplicationScoped
|
||||
public class KalenderResource {
|
||||
|
||||
@Inject
|
||||
@@ -42,10 +43,12 @@ public class KalenderResource {
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public TemplateInstance kalender(@QueryParam("offset") @DefaultValue("0") int offsetInWeeks) {
|
||||
List<KalenderTag> week = kalenderService.getWeek(offsetInWeeks);
|
||||
|
||||
return kalenderpage
|
||||
.data("today", LocalDate.now())
|
||||
.data("offset", offsetInWeeks)
|
||||
.data("week", kalenderService.getWeek(offsetInWeeks));
|
||||
.data("week", week);
|
||||
}
|
||||
|
||||
private List<Event> getEvents(int offsetInWeeks) {
|
||||
@@ -65,24 +68,24 @@ public class KalenderResource {
|
||||
LocalDate dayParsed = LocalDate.parse(day);
|
||||
|
||||
User currentUser = userService.getCurrentUser();
|
||||
Room room = currentUser.getRoom();
|
||||
try {
|
||||
KalenderTag tag = KalenderTag.findByDayAndRoomId(dayParsed, room == null ? null : room.id);
|
||||
KalenderTag tag = KalenderTag.find("day", dayParsed).singleResult();
|
||||
if (kalenderForm.isInOffice() && tag.getInOffice() == null) {
|
||||
tag.setInOffice(currentUser);
|
||||
} else if (!kalenderForm.isInOffice() && currentUser.equals(tag.getInOffice())) {
|
||||
tag.setInOffice(null);
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
KalenderTag tag = new KalenderTag(dayParsed, room);
|
||||
KalenderTag tag = new KalenderTag(dayParsed);
|
||||
tag.setInOffice(currentUser);
|
||||
tag.setRoom(room);
|
||||
tag.persist();
|
||||
}
|
||||
|
||||
return kalender(offsetInWeeks);
|
||||
}
|
||||
|
||||
// Exports
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Path("export/txt")
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package de.mbremer.kalender;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import de.mbremer.holiday.HolidayService;
|
||||
import de.mbremer.secutity.User;
|
||||
import de.mbremer.secutity.UserService;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.NoResultException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -14,25 +13,24 @@ import java.util.stream.Stream;
|
||||
|
||||
@ApplicationScoped
|
||||
public class KalenderService {
|
||||
|
||||
@Inject
|
||||
UserService userService;
|
||||
@Inject
|
||||
HolidayService holidayService;
|
||||
|
||||
public List<KalenderTag> getWeek(int offsetInWeeks) {
|
||||
List<KalenderTag> getWeek(int offsetInWeeks) {
|
||||
User currentUser = userService.getCurrentUser();
|
||||
Room room = currentUser.getRoom();
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate montag = today.minusDays(today.getDayOfWeek().getValue() - 1).plusDays(7 * offsetInWeeks);
|
||||
|
||||
return Stream.iterate(0, i -> i < 5, i -> ++i)
|
||||
.map(d -> {
|
||||
LocalDate day = montag.plusDays(d);
|
||||
KalenderTag tag;
|
||||
try {
|
||||
tag = KalenderTag.findByDayAndRoomId(day, room == null ? null : room.id);
|
||||
} catch (NoResultException e) {
|
||||
tag = new KalenderTag(day, room);
|
||||
}
|
||||
KalenderTag tag = (KalenderTag) KalenderTag.find("day", day).singleResultOptional().orElse(new KalenderTag(day));
|
||||
tag.setCurrentUserInOffice(currentUser.equals(tag.getInOffice()));
|
||||
tag.setToday(LocalDate.now().equals(day));
|
||||
tag.setHolidayname(holidayService.getHolidayNiedersachsen(day));
|
||||
return tag;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package de.mbremer.kalender;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import de.mbremer.secutity.User;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
@@ -14,31 +13,29 @@ import java.time.LocalDate;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Entity
|
||||
public class KalenderTag extends PanacheEntity {
|
||||
|
||||
private LocalDate day;
|
||||
@OneToOne
|
||||
private User inOffice;
|
||||
@OneToOne
|
||||
private Room room;
|
||||
@Transient
|
||||
private boolean currentUserInOffice;
|
||||
@Transient
|
||||
private boolean today;
|
||||
@Transient
|
||||
private String holidayname;
|
||||
|
||||
public KalenderTag(LocalDate day, Room room) {
|
||||
public KalenderTag(LocalDate day) {
|
||||
this.day = day;
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws javax.persistence.NoResultException
|
||||
*/
|
||||
public static KalenderTag findByDayAndRoomId(LocalDate day, Long roomId) {
|
||||
return roomId == null ?
|
||||
(KalenderTag) KalenderTag.find("day = ?1 and room_id is null", day).singleResult() :
|
||||
(KalenderTag) KalenderTag.find("day = ?1 and room_id = ?2", day, roomId).singleResult();
|
||||
public boolean istHoliday() {
|
||||
return holidayname != null;
|
||||
}
|
||||
|
||||
public boolean isPast() {
|
||||
return day.isBefore(LocalDate.now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package de.mbremer.room;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
public class Room extends PanacheEntity {
|
||||
@NotBlank
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package de.mbremer.room;
|
||||
|
||||
import org.jboss.resteasy.annotations.providers.multipart.PartType;
|
||||
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RoomFileForm {
|
||||
@FormParam("file")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
public InputStream file;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.mbremer.room;
|
||||
|
||||
import javax.ws.rs.FormParam;
|
||||
|
||||
public class RoomForm {
|
||||
public @FormParam("name") String name;
|
||||
|
||||
public Room getRoom() {
|
||||
return new Room(name);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package de.mbremer.room;
|
||||
|
||||
import de.mbremer.image.ImageService;
|
||||
import de.mbremer.kalender.KalenderTag;
|
||||
import de.mbremer.secutity.User;
|
||||
import de.mbremer.secutity.UserService;
|
||||
import io.quarkus.qute.Template;
|
||||
import io.quarkus.qute.TemplateInstance;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.Cache;
|
||||
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.transaction.Transactional;
|
||||
import javax.validation.ValidationException;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.quarkus.panache.common.Sort.ascending;
|
||||
|
||||
@Path("/room")
|
||||
@ApplicationScoped
|
||||
@RolesAllowed({"USER", "ADMIN"})
|
||||
public class RoomResource {
|
||||
|
||||
@Inject
|
||||
Logger log;
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
@Inject
|
||||
ImageService imageService;
|
||||
@Inject
|
||||
UserService userService;
|
||||
@Inject
|
||||
Template room;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public TemplateInstance getRoom() {
|
||||
Room currentRoom = userService.getCurrentUser().getRoom();
|
||||
return room
|
||||
.data("is_admin", identity.hasRole("ADMIN"))
|
||||
.data("rooms", Room.listAll(ascending("name")))
|
||||
.data("current_room", currentRoom == null ? "" : currentRoom.getName());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/plan")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@RolesAllowed({"ADMIN"})
|
||||
public TemplateInstance uploadRoomplan(@MultipartForm RoomFileForm roomFileForm) throws IOException {
|
||||
byte[] newRoomPlan = roomFileForm.file.readAllBytes();
|
||||
|
||||
TemplateInstance room = getRoom();
|
||||
try {
|
||||
imageService.persistRoomplan(newRoomPlan);
|
||||
} catch (ValidationException e) {
|
||||
room.data("error", e.getMessage());
|
||||
}
|
||||
return room;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/plan")
|
||||
@Cache(maxAge = 3600) // 1H
|
||||
@Produces("image/*")
|
||||
public Response getImage(@PathParam("image") String image) {
|
||||
byte[] roomplan = imageService.getRoomplan();
|
||||
return roomplan.length == 0 ? Response.noContent().build() : Response.ok(roomplan).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@Transactional
|
||||
@Path("/new")
|
||||
public TemplateInstance add(@MultipartForm RoomForm roomForm) {
|
||||
Room room = roomForm.getRoom();
|
||||
|
||||
if (Room.count("name", room.getName()) > 0) {
|
||||
return getRoom().data("error", "Der Raum " + room.getName() + " ist bereits vorhanden");
|
||||
}
|
||||
|
||||
room.persist();
|
||||
|
||||
if (Room.count() == 1) {
|
||||
log.info("Migriere alle KalenderTage ohne Rooom zu " + room.getName());
|
||||
KalenderTag.find("room is null").stream()
|
||||
.map(k -> (KalenderTag) k).forEach(k -> k.setRoom(room));
|
||||
}
|
||||
|
||||
return getRoom().data("info", "Raum " + room.getName() + " angelegt.");
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@Transactional
|
||||
@Path("/user")
|
||||
public TemplateInstance setRoom(@FormParam("room") String roomName) {
|
||||
Room room;
|
||||
try {
|
||||
room = Room.find("name", roomName).singleResult();
|
||||
} catch (NoResultException e) {
|
||||
return getRoom().data("error", "Raum " + roomName + " existiert nicht.");
|
||||
}
|
||||
User user = userService.getCurrentUser();
|
||||
user.setRoom(room);
|
||||
log.info("Setze Raum " + roomName + " für User " + user.getUsername());
|
||||
|
||||
return getRoom();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package de.mbremer.secutity;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import io.quarkus.security.jpa.*;
|
||||
import lombok.*;
|
||||
import io.quarkus.security.jpa.Password;
|
||||
import io.quarkus.security.jpa.Roles;
|
||||
import io.quarkus.security.jpa.UserDefinition;
|
||||
import io.quarkus.security.jpa.Username;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
@@ -27,36 +31,27 @@ public class User extends PanacheEntity {
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@OneToOne
|
||||
@Getter
|
||||
@Setter
|
||||
private Room room;
|
||||
|
||||
/**
|
||||
* ADMIN or USER.
|
||||
*/
|
||||
@Roles
|
||||
@Getter
|
||||
@Setter
|
||||
@Column(nullable = false)
|
||||
private String role = "USER";
|
||||
|
||||
public void setPassword(String password) {
|
||||
public User setPassword(String password) {
|
||||
this.password = BcryptUtil.bcryptHash(password);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
public User setUsername(String username) {
|
||||
this.username = username == null ? null : username.trim();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
public User setRole(String role) {
|
||||
this.role = role == null ? role : role.toUpperCase();
|
||||
}
|
||||
|
||||
public boolean hasRoleAdmin() {
|
||||
return "ADMIN".equals(role);
|
||||
}
|
||||
|
||||
public boolean hasRoleUser() {
|
||||
return "USER".equals(role);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
package de.mbremer.secutity;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
|
||||
import javax.ws.rs.FormParam;
|
||||
|
||||
public class UserForm {
|
||||
public @FormParam("username")
|
||||
String username;
|
||||
public @FormParam("password")
|
||||
String password;
|
||||
public @FormParam("passwordVerify")
|
||||
String passwordVerify;
|
||||
public @FormParam("room")
|
||||
String room;
|
||||
public @FormParam("role")
|
||||
String role;
|
||||
public @FormParam("username") String username;
|
||||
public @FormParam("password") String password;
|
||||
public @FormParam("passwordVerify") String passwordVerify;
|
||||
public @FormParam("role") String role;
|
||||
|
||||
public User getUser() {
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setRole(role);
|
||||
user.setRoom((Room) Room.find("name", room).singleResultOptional().orElse(null));
|
||||
return user;
|
||||
return new User().setUsername(username).setPassword(password).setRole(role);
|
||||
}
|
||||
|
||||
public boolean verifyPassword() {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package de.mbremer.secutity;
|
||||
|
||||
import de.mbremer.room.Room;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import io.quarkus.qute.Location;
|
||||
import io.quarkus.qute.Template;
|
||||
import io.quarkus.qute.TemplateInstance;
|
||||
@@ -11,6 +9,7 @@ import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
|
||||
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.transaction.Transactional;
|
||||
import javax.ws.rs.*;
|
||||
@@ -23,6 +22,7 @@ import static de.mbremer.secutity.Role.ADMIN;
|
||||
|
||||
@Path("/user")
|
||||
@RolesAllowed("ADMIN")
|
||||
@ApplicationScoped
|
||||
public class UserResource {
|
||||
|
||||
@Inject
|
||||
@@ -30,8 +30,6 @@ public class UserResource {
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
@Inject
|
||||
UserService userService;
|
||||
@Inject
|
||||
Template userinit;
|
||||
@Inject
|
||||
@Location("user.html")
|
||||
@@ -41,17 +39,10 @@ public class UserResource {
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@RolesAllowed({"USER", "ADMIN"})
|
||||
public TemplateInstance getUser() {
|
||||
TemplateInstance templateInstance = userTemplate
|
||||
.data("current_user", User.find("username", identity.getPrincipal().getName()).singleResult());
|
||||
|
||||
if (identity.hasRole("ADMIN")) {
|
||||
templateInstance
|
||||
.data("is_admin", true)
|
||||
.data("users", User.listAll(Sort.by("username")))
|
||||
.data("rooms", Room.listAll(Sort.by("name")));
|
||||
}
|
||||
|
||||
return templateInstance;
|
||||
return userTemplate
|
||||
.data("user_count", User.count())
|
||||
.data("current_username", identity.getPrincipal().getName())
|
||||
.data("is_admin", identity.hasRole("ADMIN"));
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -110,38 +101,6 @@ public class UserResource {
|
||||
User user = userForm.getUser();
|
||||
user.persist();
|
||||
|
||||
return getUser().data("info", "User angelegt");
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@Transactional
|
||||
@Path("/password")
|
||||
@RolesAllowed({"USER", "ADMIN"})
|
||||
public TemplateInstance changePassword(@MultipartForm UserForm userForm) {
|
||||
if (!userForm.verifyPassword()) {
|
||||
return getUser().data("error", "Das Passwort ist zu kurz oder stimmt nicht mit der Wiederholung überein.");
|
||||
}
|
||||
|
||||
userService.getCurrentUser().setPassword(userForm.password);
|
||||
|
||||
return getUser().data("info", "Passwort aktualisiert");
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
@Transactional
|
||||
@Path("/update")
|
||||
public TemplateInstance update(@MultipartForm UserForm userForm) {
|
||||
log.info("update");
|
||||
|
||||
Room room = Room.find("name", userForm.room).singleResult();
|
||||
log.info("set room " + room.getName());
|
||||
User user = User.find("username", userForm.username).singleResult();
|
||||
user.setRoom(room);
|
||||
|
||||
return getUser();
|
||||
return getUser().data("info", "User angelegt.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@ package de.mbremer.secutity;
|
||||
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.RequestScoped;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ApplicationScoped
|
||||
@RequestScoped
|
||||
public class UserService {
|
||||
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
|
||||
/**
|
||||
* Returns the user currently logged in.
|
||||
*/
|
||||
public User getCurrentUser() {
|
||||
return User.find("username", identity.getPrincipal().getName()).singleResult();
|
||||
}
|
||||
|
||||
@@ -2,23 +2,25 @@
|
||||
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://${POSTGRES_HOST:db}:${POSTGRES_PORT:5432}/buerokalender
|
||||
%prod.quarkus.datasource.username = ${POSTGRES_USER:buerokalender}
|
||||
%prod.quarkus.datasource.password = ${POSTGRES_PASSWORD:buerokalender}
|
||||
# max JDBC pool size
|
||||
%prod.quarkus.datasource.jdbc.max-size=16
|
||||
quarkus.datasource.db-kind=postgresql
|
||||
|
||||
quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect
|
||||
%dev.quarkus.hibernate-orm.log.sql=true
|
||||
|
||||
quarkus.flyway.migrate-at-start=true
|
||||
# theoretische soll auch 'filesystem:' funktionieren
|
||||
%dev.quarkus.flyway.locations=db/migration,db/dev/migration
|
||||
|
||||
# Security
|
||||
quarkus.http.auth.form.enabled=true
|
||||
#quarkus.http.auth.session.encryption-key=zHId14V+uiyxmbzhEPCyi7VvbaI80UeEO5yu0H/hVLs=
|
||||
# 24h
|
||||
quarkus.http.auth.form.timeout=24H
|
||||
quarkus.http.auth.form.timeout=86400
|
||||
|
||||
# REST-CLient
|
||||
de.mbremer.holiday.HolidayRestClient/mp-rest/url=https://deutsche-feiertage-api.de
|
||||
de.mbremer.holiday.HolidayRestClient/mp-rest/scope=javax.inject.Singleton
|
||||
|
||||
# Deployment
|
||||
quarkus.container-image.additional-tags= 1
|
||||
quarkus.container-image.group = mattbremer
|
||||
quarkus.container-image.name = buerokalender
|
||||
quarkus.container-image.name = buerokalender
|
||||
@@ -1,3 +0,0 @@
|
||||
-- passwd: admin
|
||||
insert into users(id, username, password, role) values (NEXTVAL('hibernate_sequence'), 'admin', '$2a$10$VG/tPFFYl6jc7NKpK/kc3eqVFHdjuW7dZ9Ry39s4Re2lUi0xeuzqC', 'ADMIN');
|
||||
insert into users(id, username, password, role) values (NEXTVAL('hibernate_sequence'), 'user', '$2a$10$VG/tPFFYl6jc7NKpK/kc3eqVFHdjuW7dZ9Ry39s4Re2lUi0xeuzqC', 'USER');
|
||||
@@ -1,5 +0,0 @@
|
||||
create table image (
|
||||
id bigint not null primary key,
|
||||
name varchar not null unique,
|
||||
data bytea not null
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
create table room (
|
||||
id bigint not null primary key,
|
||||
name varchar not null unique
|
||||
);
|
||||
|
||||
alter table users add column room_id bigint references room;
|
||||
alter table kalendertag add column room_id bigint references room;
|
||||
|
||||
alter table kalendertag drop constraint kalendertag_day_key;
|
||||
alter table kalendertag add constraint kalendertag_day_room_unique unique(day,room_id);
|
||||
@@ -19,14 +19,13 @@
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<div class="navbar-nav mr-auto">
|
||||
<a class="nav-item nav-link {#insert kalender_active}{/}" href="/kalender">Kalender</a>
|
||||
<a class="nav-item nav-link {#insert room_active}{/}" href="/room">Raum</a>
|
||||
<a class="nav-item nav-link {#insert user_active}{/}" href="/user">Benutzer</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary me-2" href="/user/logout">Logout</a>
|
||||
</nav>
|
||||
{#if error??}<div class="alert alert-danger" role="alert">{error}</div>{/if}
|
||||
{#if info??}<div class="alert alert-primary" role="alert">{info}</div>{/if}
|
||||
{#if error}<div class="alert alert-danger" role="alert">{error}</div>{/if}
|
||||
{#if info}<div class="alert alert-primary" role="alert">{info}</div>{/if}
|
||||
{#insert contents}No contents!{/}
|
||||
</div>
|
||||
<script src="/webjars/popper.js/2.9.2/umd/popper.min.js"></script>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<form action="/kalender/inoffice/{day.day}?offset={offset}" method="POST" name="kalenderForm" enctype="multipart/form-data">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="inoffice" onchange="this.form.submit()"
|
||||
{#if day.isPast or day.istHoliday}disabled=""{/if}"
|
||||
{#if day.isCurrentUserInOffice}checked{/if} >
|
||||
</div>
|
||||
</form>
|
||||
@@ -48,6 +49,7 @@
|
||||
{day.day.formatCommon}
|
||||
</td>
|
||||
<td>
|
||||
{#if day.istHoliday}{day.holidayname}{/if}
|
||||
{#if day.inOffice}{day.inOffice.username}{/if}
|
||||
</td>
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<div class="modal fade" id="roomModal" tabindex="-1" aria-labelledby="roomModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="roomModalLabel">Raum anlegen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form action="/room/new" method="POST" name="roomForm" enctype="multipart/form-data">
|
||||
<div class="modal-body row mb-3">
|
||||
<div class="align-items-center col-md-10 mx-auto col-lg-11">
|
||||
<div class="form-floating mb-3">
|
||||
<input type="text" name="name" class="form-control" id="name" placeholder="Raumname" required>
|
||||
<label for="name">Name</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,46 +0,0 @@
|
||||
{#include base.html}
|
||||
{#room_active}active{/}
|
||||
{#contents}
|
||||
|
||||
<div class="mt-2">
|
||||
{#if rooms.size == 0}
|
||||
Kein Raum vorhanden
|
||||
{#else}
|
||||
<h2>Raum wählen</h2>
|
||||
{/if}
|
||||
|
||||
<div class="btn-group" role="group">
|
||||
{#for room in rooms}
|
||||
<form action="/room/user" method="POST" name="chooseRoomForm" enctype="application/x-www-form-urlencoded">
|
||||
<input type="radio" class="btn-check" name="room" value="{room.name}" id="radio{room.name}" autocomplete="off"
|
||||
onchange="this.form.submit()" {#if room.name == current_room}checked{/if} >
|
||||
<label class="btn btn-outline-primary" for="radio{room.name}">{room.name}</label>
|
||||
</form>
|
||||
{/for}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<img src="/room/plan" class="img-fluid" alt="kein Raumplan vorhanden">
|
||||
</div>
|
||||
|
||||
{#if is_admin}
|
||||
<div class="mt-2">
|
||||
<form action="/room/plan" method="POST" enctype="multipart/form-data">
|
||||
<label id="filelabel" class="btn btn-secondary" for="fileinput">
|
||||
neuen Raumplan auswählen...
|
||||
</label>
|
||||
<input id="fileinput" type="file" name="file" accept="image/*" hidden
|
||||
onchange="document.getElementById('filelabel').innerHTML = 'Datei: ' + this.files[0].name;" />
|
||||
<input class="btn btn-primary" type="submit" value="Upload" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<a class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#roomModal" role="button">neuer Raum</a>
|
||||
</div>
|
||||
{#include room-modal.html}{/include}
|
||||
{/if}
|
||||
|
||||
{/contents}
|
||||
{/include}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="modal fade" id="userModal{#if id??}{id}{/if}" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -6,37 +6,25 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form action="/user/{#if update??}update{#else}new{/if}" method="POST" name="userForm" enctype="multipart/form-data">
|
||||
<form action="/user/new" method="POST" name="userForm" enctype="multipart/form-data">
|
||||
<div class="modal-body row mb-3">
|
||||
<div class="align-items-center col-md-10 mx-auto col-lg-11">
|
||||
<div class="form-floating mb-3">
|
||||
<input type="text" name="username" class="form-control" id="name" placeholder="Benutzername" required
|
||||
{#if update??}readonly value="{user.username}"{/if}>
|
||||
<input type="text" name="username" class="form-control" id="name" placeholder="Benutzername" required>
|
||||
<label for="name">Benutzername</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input type="password" name="password" class="form-control" id="pwd" placeholder="Password" required
|
||||
{#if update??}disabled value="xxxxx"{/if}>
|
||||
<input type="password" name="password" class="form-control" id="pwd" placeholder="Password" required>
|
||||
<label for="pwd">Passwort</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input type="password" name="passwordVerify" class="form-control" id="pwdv" placeholder="Passwort wiederholen" required
|
||||
{#if update??}disabled value="xxxxx"{/if}>
|
||||
<input type="password" name="passwordVerify" class="form-control" id="pwdv" placeholder="Passwort wiederholen" required>
|
||||
<label for="pwd">Passwort wiederholen</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select id="room" name="room" class="form-select" required>
|
||||
<option value="">Bitte wählen...</option>
|
||||
{#for room in rooms}
|
||||
<option {user.selectedIfIn(room)}>{room.name}</option>
|
||||
{/for}
|
||||
</select>
|
||||
<label class="col-sm-3 col-form-label1" for="room">Raum</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select id="role" name="role" class="form-select" required {#if update??}disabled{/if}>
|
||||
<option {#if update?? and user.hasRoleUser}selected{/if}>USER</option>
|
||||
<option {#if update?? and user.hasRoleAdmin}selected{/if}>ADMIN</option>
|
||||
<select id="role" name="role" class="form-select" required>
|
||||
<option selected>USER</option>
|
||||
<option>ADMIN</option>
|
||||
</select>
|
||||
<label class="col-sm-3 col-form-label1" for="role">Rolle</label>
|
||||
</div>
|
||||
|
||||
@@ -3,43 +3,17 @@
|
||||
{#contents}
|
||||
|
||||
<div class="mt-2">
|
||||
<h2>Hallo {current_user.username}</h2>
|
||||
{#if is_admin??}
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Raum</th>
|
||||
<th scope="col"/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#for user in users}
|
||||
<tr>
|
||||
<td>
|
||||
{user.username}
|
||||
</td>
|
||||
<td>
|
||||
{user.room.name??}
|
||||
</td>
|
||||
<td style="width:1px; white-space:nowrap;">
|
||||
<a class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#userModal{count}" role="button">Edit</a>
|
||||
{#include user-modal.html rooms=rooms update=true id=count user=user}{/include}
|
||||
</td>
|
||||
</tr>
|
||||
{/for}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
<h2>Hallo {current_username}</h2>
|
||||
{#if is_admin}User: {user_count}{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-2" >
|
||||
{#if is_admin??}
|
||||
<a class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#userModal" role="button">neuer Benutzer</a>
|
||||
{#if is_admin}
|
||||
<a class="btn btn-primary btn" data-bs-toggle="modal" data-bs-target="#userModal" role="button">neuer Benutzer</a>
|
||||
{/if}
|
||||
<a class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#passwordModal" role="button">Passwort ändern</a>
|
||||
{#if is_admin??}
|
||||
{#include user-modal.html rooms=rooms user=current_user}{/include}
|
||||
<a class="btn btn-primary btn" data-bs-toggle="modal" data-bs-target="#passwordModal" role="button">Passwort ändern</a>
|
||||
{#if is_admin}
|
||||
{#include user-modal.html}{/include}
|
||||
{/if}
|
||||
{#include password-modal.html}{/include}
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,8 @@ class KalenderResourceTest {
|
||||
|
||||
@InjectMock
|
||||
SecurityIdentity identity;
|
||||
@InjectMock
|
||||
KalenderService kalenderService;
|
||||
|
||||
@Test
|
||||
@TestSecurity(authorizationEnabled = false)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package de.mbremer.secutity;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheQuery;
|
||||
import io.quarkus.panache.mock.PanacheMock;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectMock;
|
||||
import io.quarkus.test.security.TestSecurity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@QuarkusTest
|
||||
class UserResourceTest {
|
||||
|
||||
@InjectMock
|
||||
SecurityIdentity identity;
|
||||
|
||||
@Test
|
||||
@TestSecurity(authorizationEnabled = false)
|
||||
void testUser() {
|
||||
Principal principal = mock(Principal.class);
|
||||
when(principal.getName()).thenReturn("user");
|
||||
when(identity.getPrincipal()).thenReturn(principal);
|
||||
|
||||
PanacheMock.mock(User.class);
|
||||
PanacheQuery panacheQuery = mock(PanacheQuery.class);
|
||||
User user = new User();
|
||||
user.setUsername("testuser");
|
||||
|
||||
when(panacheQuery.singleResult()).thenReturn(user);
|
||||
when(User.find("username", "user")).thenReturn(panacheQuery);
|
||||
|
||||
given()
|
||||
.when().get("/user")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body(containsString("Hallo testuser"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user