added some basic moduls: appliance, site, gateway, discovery, task

This commit is contained in:
Hendrik Wienhold 2023-07-24 13:29:40 +02:00
commit 2e18519fc1
60 changed files with 1787 additions and 0 deletions

50
appliance-service/pom.xml Normal file
View File

@ -0,0 +1,50 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>appliance-service</artifactId>
<name>appliance-service</name>
<description>Appliance Service</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,12 @@
package com.indu.applianceservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApplianceServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ApplianceServiceApplication.class, args);
}
}

View File

@ -0,0 +1,98 @@
package com.indu.applianceservice.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.indu.applianceservice.dto.ApplianceRequest;
import com.indu.applianceservice.dto.ApplianceResponse;
import com.indu.applianceservice.dto.SiteResponse;
import com.indu.applianceservice.exception.SiteNotFoundException;
import com.indu.applianceservice.service.ApplianceService;
import com.indu.applianceservice.utils.ApplianceState;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/appliances")
public class ApplianceController {
private final ApplianceService service;
@GetMapping(path = "/{appliance_id}")
public ResponseEntity<ApplianceResponse> getAppliance(@PathVariable Long appliance_id) {
try {
ApplianceResponse app = service.fetchById(appliance_id);
return new ResponseEntity<ApplianceResponse>( app, HttpStatus.OK);
} catch (EntityNotFoundException e) {
log.warn("Could not find appliance with id \"{}\"", appliance_id);
return new ResponseEntity<ApplianceResponse>(HttpStatus.NOT_FOUND);
} catch (Exception e) {
log.error("Unknown Exception: {}", e.getMessage());
return new ResponseEntity<ApplianceResponse>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping(path ="/site")
@ResponseStatus(HttpStatus.OK)
public List<ApplianceResponse> getAppliancesForSiteCode(@RequestParam(value="objectNumber") String object_number) {
return service.getAppliancesForObject(object_number);
}
@GetMapping
@ResponseStatus(HttpStatus.OK)
public List<ApplianceResponse> getAllAppliances() {
return service.getAllAppliances();
}
@PostMapping
public ResponseEntity<ApplianceResponse> createAppliance(@Valid @RequestBody ApplianceRequest applianceRequest) {
SiteResponse[] siteOfAppliance = service.getSiteByCode(applianceRequest.getObjectNumber());
if(siteOfAppliance.length == 0) {
throw new SiteNotFoundException(applianceRequest.getObjectNumber());
}
return new ResponseEntity<ApplianceResponse>(service.createApplianceAndRegister(applianceRequest, siteOfAppliance[0].getId()), HttpStatus.CREATED);
}
//whatev....
@GetMapping(path = "/{appliance_id}/site")
public ResponseEntity<SiteResponse> getSite(@PathVariable Long appliance_id) {
SiteResponse[] sites = service.getSiteOfId(appliance_id);
if ( sites.length == 0) {
log.warn("Found appliance with id " + appliance_id + " w/o site");
return new ResponseEntity<SiteResponse>(HttpStatus.NOT_FOUND);
} else if ( sites.length > 1 ) {
log.warn("Found appliance with id " + appliance_id + " with " + sites.length + " sites");
return new ResponseEntity<SiteResponse>(HttpStatus.CONFLICT);
}
return new ResponseEntity<SiteResponse>(sites[0] ,HttpStatus.OK);
}
@PutMapping(path ="/{appliance_id}/state")
@ResponseStatus(HttpStatus.OK)
public ApplianceResponse setState(@PathVariable Long appliance_id, ApplianceState state) {
return service.setState(appliance_id, state);
}
@GetMapping(path ="/{appliance_id}/state")
@ResponseStatus(HttpStatus.OK)
public ApplianceState setState(@PathVariable Long appliance_id) {
return service.fetchById(appliance_id).getState();
}
}

View File

@ -0,0 +1,26 @@
package com.indu.applianceservice.dto;
import com.indu.applianceservice.utils.ApplianceState;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class ApplianceRequest {
@NotNull
private String qrCode;
private String serialNumber;
@NotNull
private String objectNumber;
private String applianceClass;
private String applianceType;
private ApplianceState state;
}

View File

@ -0,0 +1,24 @@
package com.indu.applianceservice.dto;
import com.indu.applianceservice.utils.ApplianceState;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class ApplianceResponse {
private Long id;
private String qrCode;
private String serialNumber;
private String objectNumber;
private String applianceClass;
private String applianceType;
private ApplianceState state;
}

View File

@ -0,0 +1,25 @@
package com.indu.applianceservice.dto;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class SiteResponse {
private String id;
private String siteAddress;
private String siteZip;
private String siteCity;
@NotNull
private String code;
private String description;
private String state;
}

View File

@ -0,0 +1,26 @@
package com.indu.applianceservice.exception;
import java.util.Date;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.indu.applianceservice.model.ErrorMessage;
@RestControllerAdvice
public class ApplianceExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value= {SiteNotFoundException.class} )
public ResponseEntity<Object> handleSiteDuplicateException(Exception ex, WebRequest request) {
String errorMessage = ex.getLocalizedMessage();
if ( errorMessage == null) {
errorMessage = ex.toString();
}
return new ResponseEntity<>(new ErrorMessage(new Date(), errorMessage), new HttpHeaders(), HttpStatus.CONFLICT);
}
}

View File

@ -0,0 +1,16 @@
package com.indu.applianceservice.exception;
public class SiteNotFoundException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
private static String errorMessage(String code) {
return "No site with code " + code + " could be found. It is not allowed to add Aplliances w/o existing site";
}
public SiteNotFoundException(String code) {
super(errorMessage(code));
}
}

View File

@ -0,0 +1,32 @@
package com.indu.applianceservice.model;
import com.indu.applianceservice.utils.ApplianceState;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "appliances")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Appliance {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String qrCode;
private String serialNumber;
private String objectNumber;
private String applianceClass;
private String applianceType;
private ApplianceState state;
}

View File

@ -0,0 +1,17 @@
package com.indu.applianceservice.model;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorMessage {
Date timestamp;
String errorMessage;
}

View File

@ -0,0 +1,17 @@
package com.indu.applianceservice.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import com.indu.applianceservice.model.Appliance;
@Service
public interface ApplianceRepository extends JpaRepository<Appliance, Long>{
@Override
public <S extends Appliance> S save(S appliance);
List<Appliance> findByObjectNumber(String objectNumber);
}

View File

@ -0,0 +1,101 @@
package com.indu.applianceservice.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import com.indu.applianceservice.dto.ApplianceRequest;
import com.indu.applianceservice.dto.ApplianceResponse;
import com.indu.applianceservice.dto.SiteResponse;
import com.indu.applianceservice.model.Appliance;
import com.indu.applianceservice.repository.ApplianceRepository;
import com.indu.applianceservice.utils.ApplianceState;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@RequiredArgsConstructor
@Slf4j
public class ApplianceService {
private final ApplianceRepository repository;
private final WebClient.Builder webClientBuilder;
public ApplianceResponse createApplianceAndRegister(ApplianceRequest request, String site_id) {
Appliance app = Appliance.builder()
.qrCode(request.getQrCode())
.applianceClass(request.getApplianceClass())
.applianceType(request.getApplianceType())
.objectNumber(request.getObjectNumber())
.serialNumber(request.getSerialNumber())
.state(request.getState() == null ? ApplianceState.INITIAL : request.getState())
.build();
repository.save(app);
log.info("Added {} to db", app.getId());
registerAppliance(app, site_id);
return mapApplianceToResponse(app);
}
public ApplianceResponse fetchById(long id) {
Appliance app = repository.getReferenceById(id);
return mapApplianceToResponse(app);
}
private void registerAppliance(Appliance app, String site_id) {
webClientBuilder.build().patch()
.uri("http://localhost:8080/api/sites/" + site_id + "/register",
uri-> uri.queryParam("id", app.getId()).build()
).retrieve()
.bodyToMono(SiteResponse.class)
.doOnNext(res->log.info("Registered successfully app " + app.getId() + " to site " + site_id + " " + res))
.doOnError(res->log.error("Registration of app " + app.getId() + " to site " + site_id + " failed " + res))
.subscribe();
}
public List<ApplianceResponse> getAppliancesForObject(String object_number) {
List<Appliance> appliances = repository.findByObjectNumber(object_number);
return appliances.stream().map(this::mapApplianceToResponse).toList();
}
public List<ApplianceResponse> getAllAppliances() {
List<Appliance> appliances = repository.findAll();
log.info("Fetched appliances. Found {}", appliances.size());
return appliances.stream().map(this::mapApplianceToResponse).toList();
}
private ApplianceResponse mapApplianceToResponse(Appliance app) {
return ApplianceResponse.builder()
.serialNumber(app.getSerialNumber())
.id(app.getId())
.applianceClass(app.getApplianceClass())
.applianceType(app.getApplianceType())
.qrCode(app.getQrCode())
.objectNumber(app.getObjectNumber())
.state(app.getState())
.build();
}
public SiteResponse[] getSiteOfId(long id) {
Appliance app = repository.getReferenceById(id);
return getSiteByCode(app.getObjectNumber());
}
public SiteResponse[] getSiteByCode(String code) {
SiteResponse[] sites = webClientBuilder.build().get()
.uri("http://localhost:8080/api/sites",
uri->uri.queryParam("code", code).build()
).retrieve()
.bodyToMono(SiteResponse[].class)
.block();
return sites;
}
public ApplianceResponse setState(Long id, ApplianceState new_state) {
Appliance app = repository.getReferenceById(id);
app.setState(new_state);
repository.save(app);
return mapApplianceToResponse(app);
}
}

View File

@ -0,0 +1,31 @@
package com.indu.applianceservice.utils;
import lombok.Getter;
@Getter
public enum ApplianceState {
ACTIVE("active"),
LOST("lost"),
DELETED("deleted"),
INACTIVE("inactive"),
INITIAL("initial");
private final String state;
private ApplianceState(String state) {
this.state = state;
}
public String getValue() {
return state;
}
public ApplianceState of(String value) {
for (ApplianceState state : values()) {
if(state.getValue() == value) {
return state;
}
}
throw new IllegalArgumentException();
}
}

View File

@ -0,0 +1,32 @@
package com.indu.applianceservice.utils;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import lombok.extern.slf4j.Slf4j;
@Converter(autoApply = true)
@Slf4j
public class StateConverter implements AttributeConverter<ApplianceState, String>{
@Override
public String convertToDatabaseColumn(ApplianceState attribute) {
if (attribute== null) {
return null;
}
log.info(attribute.getValue());
return attribute.getValue();
}
@Override
public ApplianceState convertToEntityAttribute(String dbData) {
if(dbData == null) {
return null;
}
for (ApplianceState state : ApplianceState.values()) {
if (state.getValue().equals(dbData)) {
return state;
}
}
return null;
}
}

18
discovery-server/pom.xml Normal file
View File

@ -0,0 +1,18 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>discovery-server</artifactId>
<name>discovery-server</name>
<description>Inuducing Flow Discovery Server</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,15 @@
package com.indu.discoveryservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServiceApplication {
public static void main(String[] arg) {
SpringApplication.run(DiscoveryServiceApplication.class, arg);
}
}

32
indu-gateway/pom.xml Normal file
View File

@ -0,0 +1,32 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>indu-gateway</artifactId>
<name>indu-api-gateway</name>
<description>Api Gateway for InducingFlow</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,13 @@
package com.indu.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}

47
pom.xml Normal file
View File

@ -0,0 +1,47 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Inducing Flow3</name>
<description>Container for induflow</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.8</version>
</parent>
<modules>
<module>discovery-server</module>
<module>appliance-service</module>
<module>indu-gateway</module>
<module>site-service</module>
<module>task-service</module>
<module>workflow-service</module>
</modules>
<properties>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
<scope>provided</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
</project>

47
site-service/pom.xml Normal file
View File

@ -0,0 +1,47 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>site-service</artifactId>
<name>site-service</name>
<description>Site Service</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,12 @@
package com.indu.siteservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SiteServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SiteServiceApplication.class, args);
}
}

View File

@ -0,0 +1,24 @@
package com.indu.siteservice.configuration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import com.indu.siteservice.utils.ReadingStateConverter;
import com.indu.siteservice.utils.WritingStateConverter;
@Configuration
public class MongoDBConfiguration {
@Bean
public MongoCustomConversions customConversion() {
List<Converter<?,?>> converterList = new ArrayList<>();
converterList.add(new ReadingStateConverter());
converterList.add(new WritingStateConverter());
return new MongoCustomConversions(converterList);
}
}

View File

@ -0,0 +1,67 @@
package com.indu.siteservice.controller;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.indu.siteservice.dto.RestAddress;
import com.indu.siteservice.dto.RestSite;
import com.indu.siteservice.exceptions.SiteDuplicateException;
import com.indu.siteservice.exceptions.SiteNotFoundException;
import com.indu.siteservice.service.SiteService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/site")
public class SiteController {
private final SiteService service;
@PostMapping
public ResponseEntity<RestSite> createAppliance(@Valid @RequestBody RestSite siteRequest) {
if(service.findByCode(siteRequest).isPresent()) {
throw new SiteDuplicateException(siteRequest.getCode());
}
return new ResponseEntity<>(service.createSite(siteRequest), HttpStatus.CREATED);
}
@GetMapping(params= "code")
public ResponseEntity<RestSite > getSiteByCode(@NotNull @RequestParam(value="code") String code) {
Optional<RestSite> site = service.findByCode(code);
if ( site.isEmpty()) {
throw new SiteNotFoundException("code", code);
}
return new ResponseEntity<>(site.get(), HttpStatus.OK);
}
@GetMapping(path="/{site_id}")
@ResponseStatus(HttpStatus.OK)
public RestSite getSiteById(@PathVariable String site_id) {
return service.findById(site_id);
}
@GetMapping(path="/{site_id}/address")
@ResponseStatus(HttpStatus.OK)
public RestAddress getAddressofSite(@PathVariable String site_id) {
return service.getAddressbySiteId(site_id);
}
@PutMapping(path="/{site_id}")
@ResponseStatus(HttpStatus.OK)
public RestSite updateSiteById(@Valid @RequestBody RestSite updateData, @PathVariable String site_id) {
return service.updateById(site_id, updateData);
}
}

View File

@ -0,0 +1,42 @@
package com.indu.siteservice.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.indu.siteservice.dto.RestSite;
import com.indu.siteservice.service.SiteListService;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/sites")
public class SiteListController {
private final SiteListService service;
@GetMapping(params= "code")
public ResponseEntity<List<RestSite> > getSiteByCode(@NotNull @RequestParam(value="code") String code) {
return new ResponseEntity<>(service.findByCode(code), HttpStatus.OK);
}
@GetMapping
public ResponseEntity<List<RestSite> > getSitesAll() {
return new ResponseEntity<>(service.findAll(), HttpStatus.OK);
}
@PatchMapping(path="/{site_id}/register")
@ResponseStatus(HttpStatus.OK)
public RestSite registerAppliance(@PathVariable String site_id, @NotNull @RequestParam(value="id") long app_id){
return service.registerAppliance(site_id, app_id);
}
}

View File

@ -0,0 +1,18 @@
package com.indu.siteservice.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@Builder
@Getter
@NoArgsConstructor
public class RestAddress {
private String address;
private String zip;
private String city;
}

View File

@ -0,0 +1,25 @@
package com.indu.siteservice.dto;
import java.util.List;
import com.indu.siteservice.utils.SiteState;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class RestSite {
private String id;
private String siteAddress;
private String siteZip;
private String siteCity;
private String code;
private String description;
private SiteState state;
private List<Long> applianceIds;
}

View File

@ -0,0 +1,16 @@
package com.indu.siteservice.exceptions;
public class SiteCodeIdConflictException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
private static String errormessage(String id, String code, String correctCode) {
return "The combination of id "+ id + " and code " + code+" is invalid. The expected code for this id is "+ correctCode;
}
public SiteCodeIdConflictException(String id, String code, String correctCode) {
super(errormessage(id, code, correctCode));
}
}

View File

@ -0,0 +1,16 @@
package com.indu.siteservice.exceptions;
public class SiteDuplicateException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
private static String errormessage(String code) {
return "There already is a site with code "+code+". This value is expected to be unique";
}
public SiteDuplicateException(String code) {
super(errormessage(code));
}
}

View File

@ -0,0 +1,27 @@
package com.indu.siteservice.exceptions;
import java.util.Date;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.indu.siteservice.model.ErrorMessage;
@ControllerAdvice
public class SiteExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value= {SiteDuplicateException.class, SiteCodeIdConflictException.class, SiteNotFoundException.class} )
public ResponseEntity<Object> handleSiteDuplicateException(Exception ex, WebRequest request) {
String errorMessage = ex.getLocalizedMessage();
if ( errorMessage == null) {
errorMessage = ex.toString();
}
return new ResponseEntity<>(new ErrorMessage(new Date(), errorMessage), new HttpHeaders(), HttpStatus.CONFLICT);
}
}

View File

@ -0,0 +1,16 @@
package com.indu.siteservice.exceptions;
public class SiteNotFoundException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
private static String errormessage(String field, String code) {
return "Site with " + field + " "+code+" not found";
}
public SiteNotFoundException(String field, String code) {
super(errormessage(field, code));
}
}

View File

@ -0,0 +1,26 @@
package com.indu.siteservice.model;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class Address {
@Size(min=3)
private String city;
@Size(min=4, max=6)
@Pattern(regexp = "[1-9][0-9]+", message = "zip code may only contain numbers")
private String zip;
private String address;
}

View File

@ -0,0 +1,17 @@
package com.indu.siteservice.model;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class ErrorMessage {
private Date timestamp;
private String message;
}

View File

@ -0,0 +1,23 @@
package com.indu.siteservice.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Document(value= "seq")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Sequence {
@Id
private String id;
@Builder.Default
@NotNull
private int humanReadableId = 1;
}

View File

@ -0,0 +1,33 @@
package com.indu.siteservice.model;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.indu.siteservice.utils.SiteState;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Document(value= "site")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Site {
@Id
private String id;
private Address address;
@NotNull
private String code;
private String description;
private SiteState state;
@NotNull
@Builder.Default
private List<Long > appliances = new ArrayList<Long>();
}

View File

@ -0,0 +1,23 @@
package com.indu.siteservice.repository;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.indu.siteservice.model.Sequence;
public interface SeqRepository extends MongoRepository<Sequence, String>{
default public int getNextSequence() {
List<Sequence> docs = findAll();
if(docs.size() == 0) {
Sequence sequence = new Sequence();
save(sequence);
return 1;
}
Sequence sequence = docs.get(docs.size()-1);
sequence.setHumanReadableId(sequence.getHumanReadableId()+1);
save(sequence);
return sequence.getHumanReadableId();
}
}

View File

@ -0,0 +1,11 @@
package com.indu.siteservice.repository;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.indu.siteservice.model.Site;
public interface SiteRepository extends MongoRepository<Site, String>{
List<Site> findByCode(String code);
}

View File

@ -0,0 +1,40 @@
package com.indu.siteservice.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.indu.siteservice.dto.RestSite;
import com.indu.siteservice.model.Site;
import com.indu.siteservice.repository.SiteRepository;
import com.indu.siteservice.utils.SiteRestConverter;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class SiteListService {
private final SiteRepository repository;
public List<RestSite> findByCode(RestSite siteRequest) {
return findByCode(siteRequest.getCode());
}
public List<RestSite> findByCode(String code) {
List<Site> sites = repository.findByCode(code);
return sites.stream().map(SiteRestConverter::convert).toList();
}
public List<RestSite> findAll() {
List<Site> sites = repository.findAll();
return sites.stream().map(SiteRestConverter::convert).toList();
}
public RestSite registerAppliance(String site_id, long app_id) {
Site site = repository.findById(site_id).get();
site.getAppliances().add(app_id);
repository.save(site);
return SiteRestConverter.convert(site);
}
}

View File

@ -0,0 +1,77 @@
package com.indu.siteservice.service;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.indu.siteservice.dto.RestAddress;
import com.indu.siteservice.dto.RestSite;
import com.indu.siteservice.exceptions.SiteCodeIdConflictException;
import com.indu.siteservice.exceptions.SiteNotFoundException;
import com.indu.siteservice.model.Site;
import com.indu.siteservice.repository.SeqRepository;
import com.indu.siteservice.repository.SiteRepository;
import com.indu.siteservice.utils.SiteRestConverter;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class SiteService {
private final SiteRepository repository;
private final SeqRepository seqenceSupplier;
public RestSite createSite(RestSite siteRequest) {
Site site = SiteRestConverter.convert(siteRequest);
site.setCode(generateCustomId());
repository.save(site);
return SiteRestConverter.convert(site);
}
public Optional<RestSite> findByCode(String code) {
List<Site> sites = repository.findByCode(code);
if(sites.size() == 0) {
return Optional.empty();
}
if (sites.size() > 1) {
// throw new SiteDuplicateException(code);
// dunno what todo...
}
return Optional.of(SiteRestConverter.convert(sites.get(0)));
}
public Site findSiteObjectById(String site_id) {
return repository.findById(site_id).orElseThrow(() -> new SiteNotFoundException("boid", site_id));
}
public Optional<RestSite> findByCode(@Valid RestSite siteRequest) {
return findByCode(siteRequest.getCode());
}
private String generateCustomId() {
return String.format("O-%06d", seqenceSupplier.getNextSequence());
}
public RestSite findById(String site_id) {
return SiteRestConverter.convert( findSiteObjectById(site_id));
}
public RestAddress getAddressbySiteId(String site_id) {
return SiteRestConverter.convert(findSiteObjectById(site_id).getAddress());
}
public RestSite updateById(String site_id, @Valid RestSite updateData) {
Site site = findSiteObjectById(site_id);
if ( !site.getCode().equals(updateData.getCode())) {
throw new SiteCodeIdConflictException(site_id, updateData.getCode(), site.getCode());
}
Site updatedSite = SiteRestConverter.convert(updateData);
updatedSite.setId(site.getId());
repository.save(updatedSite);
return SiteRestConverter.convert(updatedSite);
}
}

View File

@ -0,0 +1,27 @@
package com.indu.siteservice.utils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ReadingConverter
//@Converter(autoApply = true)
public class ReadingStateConverter implements Converter<String, SiteState> {
@Override
public SiteState convert(String source) {
// TODO Auto-generated method stub
if (source == null) {
return null;
}
for (SiteState state : SiteState.values()) {
if (state.getValue().equals(source)) {
return state;
}
}
return null;
}
}

View File

@ -0,0 +1,59 @@
package com.indu.siteservice.utils;
import java.util.ArrayList;
import com.indu.siteservice.dto.RestAddress;
import com.indu.siteservice.dto.RestSite;
import com.indu.siteservice.model.Address;
import com.indu.siteservice.model.Site;
public class SiteRestConverter {
public static RestSite convert(Site source) {
return RestSite.builder()
.siteAddress(source.getAddress().getAddress())
.siteZip(source.getAddress().getZip())
.siteCity(source.getAddress().getCity())
.code(source.getCode())
.state(source.getState())
.description(source.getDescription())
.applianceIds(source.getAppliances())
.id(source.getId())
.build();
}
public static Site convert(RestSite source) {
Address siteAddress = Address.builder()
.zip(source.getSiteZip())
.address(source.getSiteAddress())
.city(source.getSiteCity())
.build();
return Site.builder()
.address(siteAddress)
.code(source.getCode())
.appliances(source.getApplianceIds() == null ? new ArrayList<Long>() : source.getApplianceIds())
.state(source.getState())
.id(source.getId())
.description(source.getDescription())
.build();
}
public static RestAddress convert(Address address) {
return RestAddress.builder()
.address(address.getAddress())
.city(address.getCity())
.zip(address.getZip())
.build();
}
public static Address convert(RestAddress address) {
return Address.builder()
.address(address.getAddress())
.city(address.getCity())
.zip(address.getZip())
.build();
}
}

View File

@ -0,0 +1,16 @@
package com.indu.siteservice.utils;
public enum SiteState {
AKTIVE("active"),
INACTIVE("inactive"),
DELETED("deleted");
private final String state;
private SiteState(String state) {
this.state = state;
}
public String getValue() {
return state;
}
}

View File

@ -0,0 +1,33 @@
package com.indu.siteservice.utils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@WritingConverter
//@Converter(autoApply = true)
public class WritingStateConverter implements Converter<SiteState, String> {
@Override
public String convert(SiteState source) {
// TODO Auto-generated method stub
if (source == null) {
return null;
}
return source.getValue();
}
}
/*
* @Override public String convertToDatabaseColumn(SiteState attribute) { if
* (attribute== null) { return null; } log.info(attribute.getValue()); return
* attribute.getValue(); }
*
* @Override public SiteState convertToEntityAttribute(String dbData) {
* if(dbData == null) { return null; } for (SiteState state :
* SiteState.values()) { if (state.getValue().equals(dbData)) { return state; }
* } return null; }
*/

73
task-service/pom.xml Normal file
View File

@ -0,0 +1,73 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.indu</groupId>
<artifactId>indu-flow-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>task-service</artifactId>
<name>task-service</name>
<description>Task Service</description>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package com.indu.taskservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TaskApplication {
public static void main(String[] args) {
SpringApplication.run(TaskApplication.class, args);
}
}

View File

@ -0,0 +1,50 @@
package com.indu.taskservice.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.indu.taskservice.dto.TaskRequest;
import com.indu.taskservice.dto.TaskResponse;
import com.indu.taskservice.dto.TaskStateRequest;
import com.indu.taskservice.service.TaskService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/tasks")
public class TaskController {
private final TaskService service;
@GetMapping
@ResponseStatus(HttpStatus.OK)
public List<TaskResponse> getAllTasks() {
return service.getAllTasks();
}
@PostMapping
@ResponseStatus(HttpStatus.OK)
public TaskResponse createTask(@RequestBody TaskRequest request) {
return service.createTask(request);
}
@GetMapping(path="/{id}")
public TaskResponse getTaskById(@RequestParam String id) {
return service.getById(id);
}
@PatchMapping(path="{id}/state")
public TaskResponse setState(@RequestParam String id, @Valid @RequestBody TaskStateRequest taskRequest) {
return service.updateState(id, taskRequest);
}
}

View File

@ -0,0 +1,19 @@
package com.indu.taskservice.dto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import com.indu.taskservice.model.Task;
@Mapper
public interface TaskMapper {
TaskMapper instance = Mappers.getMapper(TaskMapper.class);
TaskResponse taskToResponse(Task task);
Task requestToTask(TaskRequest task);
@Mapping(target = "id", ignore = true)
void updateTaskFromRequest(TaskRequest request, @MappingTarget Task task);
}

View File

@ -0,0 +1,29 @@
package com.indu.taskservice.dto;
import java.util.Date;
import java.util.Map;
import com.indu.taskservice.model.TaskState;
import com.indu.taskservice.model.User;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TaskRequest {
private String id;
@NotNull
private TaskState state;
private Map<String, String> data;
private User editor;
@NotNull
private Date lastEdited;
}

View File

@ -0,0 +1,17 @@
package com.indu.taskservice.dto;
import com.indu.taskservice.model.TaskState;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TaskResponse {
private String id;
private TaskState state;
}

View File

@ -0,0 +1,25 @@
package com.indu.taskservice.dto;
import java.util.Date;
import com.indu.taskservice.model.TaskState;
import com.indu.taskservice.model.User;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TaskStateRequest {
@NotNull
private TaskState state;
@NotNull
private User editor;
@NotNull
private Date lastEdited;
}

View File

@ -0,0 +1,15 @@
package com.indu.taskservice.exception;
import com.indu.taskservice.model.TaskState;
public class StateTransitionNotAllowedException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 4066544793849348805L;
public StateTransitionNotAllowedException(TaskState from, TaskState to) {
super("State transition from " + from.name() +" to " + to.name() +" is not allowed");
}
}

View File

@ -0,0 +1,13 @@
package com.indu.taskservice.exception;
public class TaskChangedException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 4066544793849348805L;
public TaskChangedException() {
super("This task changed since last call. Retry");
}
}

View File

@ -0,0 +1,27 @@
package com.indu.taskservice.exception;
import java.util.Date;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.indu.taskservice.model.ErrorMessage;
@ControllerAdvice
public class TaskExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler( value= {TaskChangedException.class, StateTransitionNotAllowedException.class})
public ResponseEntity<Object> handleTaskAlreadyChangedException(Exception e, WebRequest request) {
String errorMessage = e.getLocalizedMessage();
if ( errorMessage == null) {
errorMessage = e.toString();
}
return new ResponseEntity<>(new ErrorMessage(new Date(), errorMessage), new HttpHeaders(), HttpStatus.CONFLICT);
}
}

View File

@ -0,0 +1,5 @@
package com.indu.taskservice.model;
public class Action {
}

View File

@ -0,0 +1,17 @@
package com.indu.taskservice.model;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class ErrorMessage {
private Date timestamp;
private String message;
}

View File

@ -0,0 +1,28 @@
package com.indu.taskservice.model;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Document(value="task")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Task {
private String id;
@Builder.Default
private TaskState state = TaskState.WAITING;
@Builder.Default
private Map<String, String> data = new HashMap();
private User editor;
@Builder.Default
private Date lastEdited = new Date();
}

View File

@ -0,0 +1,16 @@
package com.indu.taskservice.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TaskState {
WAITING(3),
READY(5),
IN_PROGRESS(8),
DONE(10),
ABORT(-10);
private final int state;
}

View File

@ -0,0 +1,14 @@
package com.indu.taskservice.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String firstName;
private String lastName;
private String id;
}

View File

@ -0,0 +1,13 @@
package com.indu.taskservice.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.indu.taskservice.model.Task;
public interface TaskRepository extends MongoRepository<Task, String>{
@Override
Task save(Task entity);
}

View File

@ -0,0 +1,61 @@
package com.indu.taskservice.service;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Service;
import com.indu.taskservice.dto.TaskMapper;
import com.indu.taskservice.dto.TaskRequest;
import com.indu.taskservice.dto.TaskResponse;
import com.indu.taskservice.dto.TaskStateRequest;
import com.indu.taskservice.exception.StateTransitionNotAllowedException;
import com.indu.taskservice.exception.TaskChangedException;
import com.indu.taskservice.model.Task;
import com.indu.taskservice.repository.TaskRepository;
import jakarta.validation.Valid;
import jakarta.ws.rs.NotFoundException;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class TaskService {
private final TaskRepository repository;
public List<TaskResponse> getAllTasks() {
return repository.findAll().stream().map(task -> TaskMapper.instance.taskToResponse(task)).toList();
}
public TaskResponse createTask(TaskRequest request) {
Task task = TaskMapper.instance.requestToTask(request);
repository.save(task);
return TaskMapper.instance.taskToResponse(task);
}
public Task getTaskById(String id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException("did not find required task"));
}
public TaskResponse getById(String id) {
return TaskMapper.instance.taskToResponse(getTaskById(id));
}
//TODO workflow
public TaskResponse updateState(String id, @Valid TaskStateRequest taskRequest) {
Task task = getTaskById(id);
if(!task.getLastEdited().equals(taskRequest.getLastEdited())) {
throw new TaskChangedException();
}
if(!TaskStateTransitionValidator.isTransitionAllowed(task.getState(), taskRequest.getState())) {
throw new StateTransitionNotAllowedException(task.getState(), taskRequest.getState());
}
task.setEditor(taskRequest.getEditor());
task.setLastEdited(new Date());
task.setState(taskRequest.getState());
repository.save(task);
return TaskMapper.instance.taskToResponse(task);
}
}

View File

@ -0,0 +1,26 @@
package com.indu.taskservice.service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.indu.taskservice.model.TaskState;
public class TaskStateTransitionValidator {
private final static Map<TaskState, List<TaskState>> allowedTransitions = new HashMap<TaskState, List<TaskState>>();
static {
allowedTransitions.put(TaskState.WAITING, Arrays.asList(TaskState.READY, TaskState.DONE, TaskState.ABORT));
allowedTransitions.put(TaskState.READY, Arrays.asList(TaskState.IN_PROGRESS, TaskState.ABORT));
allowedTransitions.put(TaskState.IN_PROGRESS, Arrays.asList(TaskState.READY, TaskState.DONE, TaskState.ABORT));
allowedTransitions.put(TaskState.DONE, Arrays.asList(TaskState.ABORT));
allowedTransitions.put(TaskState.ABORT, Arrays.asList());
}
public static boolean isTransitionAllowed(TaskState fromState, TaskState toState) {
// for now throw nullpointerexception if allowedTransion if state gets add to enum, that is missing here
return allowedTransitions.get(toState).contains(toState);
}
}