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