PatrixCrud, PatrixMqtt, PatrixDemo
This commit is contained in:
commit
0fa2238557
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
42
PatrixCrud/pom.xml
Normal file
42
PatrixCrud/pom.xml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>PatrixCrud</artifactId>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixParent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
public enum CrudAction {
|
||||||
|
CREATED, MODIFIED, DELETED
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class CrudController<Entity, ID, Create, Dto, Repository extends CrudRepository<Entity, ID, Dto>, Service extends CrudService<Entity, ID, Create, Dto, Repository>> {
|
||||||
|
|
||||||
|
protected final Service service;
|
||||||
|
|
||||||
|
protected final Repository repository;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public Dto byId(@PathVariable @NonNull final ID id) {
|
||||||
|
return repository.getDtoById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@PostMapping("list")
|
||||||
|
public List<Dto> list(@RequestBody @NonNull final CrudFilter<Entity> filter) {
|
||||||
|
return repository.findAllDto(filter.getSpecification(), filter.getSort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@PostMapping("page")
|
||||||
|
public Page<Dto> page(@RequestBody @NonNull final CrudFilter<Entity> filter) {
|
||||||
|
return repository.findAllDto(filter.getSpecification(), filter.getPageable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@PostMapping("create")
|
||||||
|
public Dto create(@RequestBody @NonNull final Create create) {
|
||||||
|
return service.create(create);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
public Dto delete(@PathVariable @NonNull final ID id) {
|
||||||
|
return service.delete(id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
23
PatrixCrud/src/main/java/de/ph87/patrix/crud/CrudEvent.java
Normal file
23
PatrixCrud/src/main/java/de/ph87/patrix/crud/CrudEvent.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CrudEvent<T> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final ZonedDateTime timestamp = ZonedDateTime.now();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String topic;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final T payload;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final CrudAction action;
|
||||||
|
|
||||||
|
}
|
||||||
50
PatrixCrud/src/main/java/de/ph87/patrix/crud/CrudFilter.java
Normal file
50
PatrixCrud/src/main/java/de/ph87/patrix/crud/CrudFilter.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CrudFilter<T> {
|
||||||
|
|
||||||
|
public final int page;
|
||||||
|
|
||||||
|
public final int size;
|
||||||
|
|
||||||
|
public final List<Order> orders;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Sort getSort() {
|
||||||
|
return Sort.by(orders.stream().map(Order::toSortOrder).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Order {
|
||||||
|
|
||||||
|
public final String property;
|
||||||
|
|
||||||
|
public final Sort.Direction direction;
|
||||||
|
|
||||||
|
public Sort.Order toSortOrder() {
|
||||||
|
return new Sort.Order(direction, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Pageable getPageable() {
|
||||||
|
return PageRequest.of(page, size, getSort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Specification<T> getSpecification() {
|
||||||
|
return Specification.where(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
import org.springframework.data.repository.ListCrudRepository;
|
||||||
|
import org.springframework.data.repository.NoRepositoryBean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NoRepositoryBean
|
||||||
|
public interface CrudRepository<Entity, ID, Dto> extends ListCrudRepository<Entity, ID>, JpaSpecificationExecutor<Entity> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
default List<Dto> findAllDto(@Nullable final Specification<Entity> specification, @NonNull final Sort sort) {
|
||||||
|
return findAll(specification, sort).stream().map(this::toDto).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
default Page<Dto> findAllDto(@NonNull final Specification<Entity> specification, @NonNull final Pageable pageable) {
|
||||||
|
return findAll(specification, pageable).map(this::toDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
default Dto getDtoById(@NonNull ID id) {
|
||||||
|
return findById(id).map(this::toDto).orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Dto toDto(@NonNull final Entity entity);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class CrudService<Entity, ID, Create, Dto, Repository extends CrudRepository<Entity, ID, Dto>> {
|
||||||
|
|
||||||
|
protected final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
protected final Repository repository;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Transactional
|
||||||
|
public Dto create(@NonNull final Create create) {
|
||||||
|
return publish(repository.save(toEntity(create)), CrudAction.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Transactional
|
||||||
|
public Dto modify(@NonNull final ID id, @NonNull final Consumer<Entity> modifier) {
|
||||||
|
final Entity entity = repository.findById(id).orElseThrow();
|
||||||
|
modifier.accept(entity);
|
||||||
|
return publish(entity, CrudAction.MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Transactional
|
||||||
|
public Dto delete(@NonNull final ID id, @Nullable final Consumer<Entity> preDelete) {
|
||||||
|
final Entity entity = repository.findById(id).orElseThrow();
|
||||||
|
final Dto dto = publish(entity, CrudAction.DELETED);
|
||||||
|
if (preDelete != null) {
|
||||||
|
preDelete.accept(entity);
|
||||||
|
}
|
||||||
|
repository.delete(entity);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected Dto publish(@NonNull final Entity entity, @NonNull final CrudAction action) {
|
||||||
|
final Dto dto = repository.toDto(entity);
|
||||||
|
log.info("{} {}: {}", entity.getClass().getSimpleName(), action, dto);
|
||||||
|
applicationEventPublisher.publishEvent(new CrudEvent<>(entity.getClass().getSimpleName(), dto, action));
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected abstract Entity toEntity(@NonNull final Create create);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CrudWebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(@NonNull final CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||||
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||||
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocketMessageBroker
|
||||||
|
public class CrudWebsocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerStompEndpoints(@NonNull final StompEndpointRegistry registry) {
|
||||||
|
registry.addEndpoint("/websocket").setAllowedOrigins("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureMessageBroker(@NonNull final MessageBrokerRegistry config) {
|
||||||
|
config.enableSimpleBroker().setHeartbeatValue(new long[]{2000, 2000}).setTaskScheduler(heartBeatScheduler());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TaskScheduler heartBeatScheduler() {
|
||||||
|
return new ThreadPoolTaskScheduler();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package de.ph87.patrix.crud;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CrudWebsocketService {
|
||||||
|
|
||||||
|
private final SimpMessageSendingOperations simpMessageSendingOperations;
|
||||||
|
|
||||||
|
@TransactionalEventListener(CrudEvent.class)
|
||||||
|
public void send(@NonNull final CrudEvent<?> event) {
|
||||||
|
simpMessageSendingOperations.convertAndSend(event.getTopic(), event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
PatrixCrud/src/main/resources/application.properties
Normal file
10
PatrixCrud/src/main/resources/application.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
logging.level.root=WARN
|
||||||
|
logging.level.de.ph87=INFO
|
||||||
|
#-
|
||||||
|
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.open-in-view=false
|
||||||
|
#-
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
#-
|
||||||
|
spring.main.banner-mode=off
|
||||||
32
PatrixDemo/pom.xml
Normal file
32
PatrixDemo/pom.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>PatrixDemo</artifactId>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixParent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixCrud</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixMqtt</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
23
PatrixDemo/src/main/java/de/ph87/patrix/demo/Backend.java
Normal file
23
PatrixDemo/src/main/java/de/ph87/patrix/demo/Backend.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package de.ph87.patrix.demo;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@ComponentScan(basePackages = {"de.ph87.patrix"})
|
||||||
|
public class Backend {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Backend.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package de.ph87.patrix.demo;
|
||||||
|
|
||||||
|
import de.ph87.patrix.demo.user.UserController;
|
||||||
|
import de.ph87.patrix.demo.user.UserCreate;
|
||||||
|
import de.ph87.patrix.demo.user.UserDto;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DemoService {
|
||||||
|
|
||||||
|
private final UserController userController;
|
||||||
|
|
||||||
|
@EventListener(ApplicationStartedEvent.class)
|
||||||
|
public void insertDemoData() {
|
||||||
|
final UserDto patrick = userController.create(new UserCreate(true, "patrick", "1234", "Patrick", "Haßel"));
|
||||||
|
final UserDto katrin = userController.create(new UserCreate(true, "katrin", null, "Katrin", "Haßel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class DuplicateUser extends RuntimeException {
|
||||||
|
|
||||||
|
public DuplicateUser() {
|
||||||
|
super("Duplicate user");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class PasswordNotAllowed extends RuntimeException {
|
||||||
|
|
||||||
|
public PasswordNotAllowed(final String reason) {
|
||||||
|
super("Password not allowed: " + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
PatrixDemo/src/main/java/de/ph87/patrix/demo/user/User.java
Normal file
71
PatrixDemo/src/main/java/de/ph87/patrix/demo/user/User.java
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Table(name = "`user`")
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
private long version;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
private ZonedDateTime created = ZonedDateTime.now();
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String firstname = "";
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@NonNull
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String lastname = "";
|
||||||
|
|
||||||
|
public User(@NonNull final UserCreate create, @NonNull final PasswordEncoder passwordEncoder) {
|
||||||
|
this.enabled = create.enabled;
|
||||||
|
this.name = create.name;
|
||||||
|
this.setPassword(create.password, passwordEncoder);
|
||||||
|
this.firstname = create.firstname;
|
||||||
|
this.lastname = create.lastname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void setPassword(@Nullable final String password, @NonNull final PasswordEncoder passwordEncoder) {
|
||||||
|
if (password == null || password.isEmpty()) {
|
||||||
|
this.password = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.length() < 4) {
|
||||||
|
throw new PasswordNotAllowed("Too short");
|
||||||
|
}
|
||||||
|
this.password = passwordEncoder.encode(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import de.ph87.patrix.crud.CrudController;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("User")
|
||||||
|
public class UserController extends CrudController<User, Long, UserCreate, UserDto, UserRepository, UserService> {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserController(final UserService service, final UserRepository repository, final PasswordEncoder passwordEncoder) {
|
||||||
|
super(service, repository);
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("{id}/name")
|
||||||
|
public UserDto name(@PathVariable final long id, @RequestBody @NonNull final String name) {
|
||||||
|
return service.modify(id, user -> {
|
||||||
|
if (user.getName().equals(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (repository.existsByName(name)) {
|
||||||
|
throw new DuplicateUser();
|
||||||
|
}
|
||||||
|
user.setName(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("{id}/password")
|
||||||
|
public UserDto password(@PathVariable final long id, @RequestBody(required = false) @Nullable final String password) {
|
||||||
|
return service.modify(id, user -> user.setPassword(password, passwordEncoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("{id}/firstname")
|
||||||
|
public UserDto firstname(@PathVariable final long id, @RequestBody @NonNull final String firstname) {
|
||||||
|
return service.modify(id, user -> user.setFirstname(firstname));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("{id}/lastname")
|
||||||
|
public UserDto lastname(@PathVariable final long id, @RequestBody @NonNull final String lastname) {
|
||||||
|
return service.modify(id, user -> user.setLastname(lastname));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserCreate {
|
||||||
|
|
||||||
|
public final boolean enabled;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final String password;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String firstname;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String lastname;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class UserDto {
|
||||||
|
|
||||||
|
public final long id;
|
||||||
|
|
||||||
|
public final long version;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ToString.Exclude
|
||||||
|
public final ZonedDateTime created;
|
||||||
|
|
||||||
|
public final boolean enabled;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public final boolean password;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String firstname;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final String lastname;
|
||||||
|
|
||||||
|
public UserDto(@NonNull final User user) {
|
||||||
|
this.id = user.getId();
|
||||||
|
this.version = user.getVersion();
|
||||||
|
this.created = user.getCreated();
|
||||||
|
this.enabled = user.isEnabled();
|
||||||
|
this.name = user.getName();
|
||||||
|
this.password = !user.getPassword().isEmpty();
|
||||||
|
this.firstname = user.getFirstname();
|
||||||
|
this.lastname = user.getLastname();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import de.ph87.patrix.crud.CrudFilter;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class UserFilter extends CrudFilter<User> {
|
||||||
|
|
||||||
|
public UserFilter(final int page, final int size, final List<Order> orders) {
|
||||||
|
super(page, size, orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import de.ph87.patrix.crud.CrudRepository;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
public interface UserRepository extends CrudRepository<User, Long, UserDto> {
|
||||||
|
|
||||||
|
boolean existsByName(@NonNull String name);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
default UserDto toDto(@NonNull final User user) {
|
||||||
|
return new UserDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package de.ph87.patrix.demo.user;
|
||||||
|
|
||||||
|
import de.ph87.patrix.crud.CrudService;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class UserService extends CrudService<User, Long, UserCreate, UserDto, UserRepository> {
|
||||||
|
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserService(final UserRepository repository, final ApplicationEventPublisher applicationEventPublisher, final PasswordEncoder passwordEncoder) {
|
||||||
|
super(applicationEventPublisher, repository);
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public UserDto create(@NonNull final UserCreate userCreate) {
|
||||||
|
if (repository.existsByName(userCreate.name)) {
|
||||||
|
throw new DuplicateUser();
|
||||||
|
}
|
||||||
|
return super.create(userCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected User toEntity(@NonNull final UserCreate create) {
|
||||||
|
return new User(create, passwordEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
PatrixDemo/src/main/resources/application.properties
Normal file
10
PatrixDemo/src/main/resources/application.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
logging.level.root=WARN
|
||||||
|
logging.level.de.ph87=INFO
|
||||||
|
#-
|
||||||
|
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.open-in-view=false
|
||||||
|
#-
|
||||||
|
spring.jackson.serialization.indent_output=true
|
||||||
|
#-
|
||||||
|
spring.main.banner-mode=off
|
||||||
31
PatrixMqtt/pom.xml
Normal file
31
PatrixMqtt/pom.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>PatrixMqtt</artifactId>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixParent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hivemq</groupId>
|
||||||
|
<artifactId>hivemq-mqtt-client</artifactId>
|
||||||
|
<version>1.3.5</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
32
PatrixMqtt/src/main/java/de/ph87/patrix/mqtt/MqttConfig.java
Normal file
32
PatrixMqtt/src/main/java/de/ph87/patrix/mqtt/MqttConfig.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package de.ph87.patrix.mqtt;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "de.ph87.patrix.mqtt")
|
||||||
|
public class MqttConfig {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String host = "";
|
||||||
|
|
||||||
|
private int port = 1883;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String clientId = "TEMPORARY-" + UUID.randomUUID();
|
||||||
|
|
||||||
|
private boolean cleanSession = true;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getUrl() {
|
||||||
|
return "tcp://" + host + ":" + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
PatrixMqtt/src/main/java/de/ph87/patrix/mqtt/MqttEvent.java
Normal file
30
PatrixMqtt/src/main/java/de/ph87/patrix/mqtt/MqttEvent.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package de.ph87.patrix.mqtt;
|
||||||
|
|
||||||
|
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class MqttEvent {
|
||||||
|
|
||||||
|
public final ZonedDateTime received = ZonedDateTime.now();
|
||||||
|
|
||||||
|
public final String topic;
|
||||||
|
|
||||||
|
@ToString.Exclude
|
||||||
|
public final String payload;
|
||||||
|
|
||||||
|
public final String payloadLoggable;
|
||||||
|
|
||||||
|
public MqttEvent(@NonNull final Mqtt3Publish publish) {
|
||||||
|
this.topic = publish.getTopic().toString();
|
||||||
|
this.payload = new String(publish.getPayloadAsBytes(), StandardCharsets.UTF_8);
|
||||||
|
this.payloadLoggable = this.payload.replace("\n", "\\n").replace("\r", "\\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package de.ph87.patrix.mqtt;
|
||||||
|
|
||||||
|
import com.hivemq.client.mqtt.MqttClient;
|
||||||
|
import com.hivemq.client.mqtt.datatypes.MqttQos;
|
||||||
|
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient;
|
||||||
|
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MqttService {
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private final MqttConfig config;
|
||||||
|
|
||||||
|
private Mqtt3AsyncClient client;
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void connect() {
|
||||||
|
if (client != null) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
client = MqttClient.builder()
|
||||||
|
.useMqttVersion3()
|
||||||
|
.identifier(config.getClientId())
|
||||||
|
.serverHost(config.getHost())
|
||||||
|
.serverPort(config.getPort())
|
||||||
|
.buildAsync();
|
||||||
|
client.connectWith().cleanSession(config.isCleanSession()).send();
|
||||||
|
client.subscribeWith().topicFilter("#").qos(MqttQos.AT_LEAST_ONCE).callback(this::receive).send();
|
||||||
|
log.info("Connected: {} as {} {}", config.getUrl(), config.getClientId(), config.isCleanSession() ? "[CLEAN SESSION]" : "[PERSISTENT SESSION]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdown() {
|
||||||
|
if (client != null) {
|
||||||
|
log.info("Disconnecting...");
|
||||||
|
client.disconnect();
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(@NonNull final Mqtt3Publish publish) {
|
||||||
|
try {
|
||||||
|
final MqttEvent event = new MqttEvent(publish);
|
||||||
|
log.debug("Message received: topic={}, payload={}", event.getTopic(), event.getPayloadLoggable());
|
||||||
|
applicationEventPublisher.publishEvent(event);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unexpected Exception publishing MqttEvent.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
application.properties
Normal file
10
application.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
logging.level.de.ph87=DEBUG
|
||||||
|
#-
|
||||||
|
spring.datasource.url=jdbc:h2:./database;AUTO_SERVER=TRUE
|
||||||
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
spring.datasource.username=sa
|
||||||
|
spring.datasource.password=password
|
||||||
|
#-
|
||||||
|
spring.jpa.hibernate.ddl-auto=create
|
||||||
|
#-
|
||||||
|
de.ph87.patrix.mqtt.host=10.0.0.50
|
||||||
51
pom.xml
Normal file
51
pom.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<maven.compiler.release>21</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixParent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>PatrixCrud</module>
|
||||||
|
<module>PatrixDemo</module>
|
||||||
|
<module>PatrixMqtt</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.4.4</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixCrud</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixMqtt</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.ph87.patrix</groupId>
|
||||||
|
<artifactId>PatrixDemo</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
</project>
|
||||||
Loading…
Reference in New Issue
Block a user